nodejs

Node.js是运行在服务端的JavaScript。

Node.js是一个基于Chrome JavaScript运行时建立的一个平台。

Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。



Node.js教程适用人群

对于不会运用Python、PHP以及Java等动态编程语言的前端程序员来说,选择Node.js作为一个创建自己的服务的工具是非常明智的。

Node.js是运行在服务端的JavaScript,因此,熟悉Javascript的使用将有助于学习Node.js。

同时,学习该Node.js教程也可以帮助后端程序员部署一些高性能的服务。


学习本教程前你需要了解

在继续本教程之前,你应该了解一些基本的计算机编程术语。如果你学习过Javascript,PHP,Java等编程语言,将有助于你更快的了解Node.js编程。


第一个Node.js程序:Hello World!

脚本模式

以下是我们的第一个Node.js程序:

console.log("Hello World");

保存该文件,文件名为helloworld.js,并通过node命令来执行:

node helloworld.js

程序执行后,正常的话,就会在终端输出Hello World。

交互模式

打开终端,键入node进入命令交互模式,可以输入一条代码语句后立即执行并显示结果,例如:

$ node> console.log('Hello World!');Hello World!

相关教程

JavaScript教程

PHP教程

Java教程

本章节我们将向大家介绍在window和Linux上安装Node.js的方法。

本安装教程以 Node.js v4.4.3 LTS(长期支持版本)版本为例。

Node.js 安装包及源码下载地址为:https://nodejs.org/en/download/


你可以根据不同平台系统选择你需要的 Node.js 安装包。

Node.js 历史版本下载地址:https://nodejs.org/dist/

注意:Linux 上安装 Node.js 需要安装 Python 2.6 或 2.7 ,不建议安装 Python 3.0 以上版本。


Windowv 上安装Node.js

1、Windows 安装包(.msi)

32 位安装包下载地址 : http://nodejs.org/dist/v0.10.26/node-v0.10.26-x86.msi

64 位安装包下载地址 : http://nodejs.org/dist/v0.10.26/x64/node-v0.10.26-x64.msi


本文实例以 v0.10.26 版本为例,其他版本类似。

安装步骤:

步骤 1 : 双击下载后的安装包 node-v0.10.26-x86.msi,如下所示:

install-node-msi-version-on-windows-step1

步骤 2 : 点击以上的Run(运行),将出现如下界面:

install-node-msi-version-on-windows-step2

步骤 3 : 勾选接受协议选项,点击 next(下一步) 按钮 :

install-node-msi-version-on-windows-step3

步骤 4 : Node.js默认安装目录为 "C:Program Files odejs" , 你可以修改目录,并点击 next(下一步):

install-node-msi-version-on-windows-step4

步骤 5 : 点击树形图标来选择你需要的安装模式 , 然后点击 next 进入下一步

install-node-msi-version-on-windows-step5

步骤 6 :点击 Install(安装) 开始安装Node.js。你也可以点击 Back(返回)来修改先前的配置。 然后并点击 next进入下一步:

install-node-msi-version-on-windows-step6

安装过程:

install-node-msi-version-on-windows-step7

点击 Finish(完成)按钮退出安装向导。

install-node-msi-version-on-windows-step8

检测PATH环境变量是否配置了Node.js,点击开始=》运行=》输入"cmd" => 输入命令"path",输出如下结果:

PATH=C:oraclexeapporacleproduct10.2.0serverin;C:Windowssystem32;C:Windows;C:WindowsSystem32Wbem;C:WindowsSystem32WindowsPowerShellv1.0;c:python32python;C:MinGWin;C:Program FilesGTK2-Runtimelib;C:Program FilesMySQLMySQL Server 5.5in;C:Program Files
odejs;C:Users
gAppDataRoaming
pm

我们可以看到环境变量中已经包含了 C:Program Files odejs

检查Node.js版本

node-version-test

2、Windows 二进制文件 (.exe)安装

32 位安装包下载地址 : http://nodejs.org/dist/v0.10.26/node.exe

64 位安装包下载地址 : http://nodejs.org/dist/v0.10.26/x64/node.exe

安装步骤

步骤 1 : 双击下载的安装包 Node.exe ,将出现如下界面 :

install-node-exe-on-windows-step1

点击 Run(运行)按钮将出现命令行窗口:

install-node-exe-on-windows-step21

版本测试:

输入截图中命令,进入 node.exe 所在的目录,如下所示:

node-version

如果你获得以上输出结果,说明你已经成功安装了Node.js。


Linux上安装 Node.js

直接使用已编译好的包

Node 官网已经把 linux 下载版本更改为已编译好的版本了,我们可以直接下载解压后使用:

# wget https://nodejs.org/dist/v10.9.0/node-v10.9.0-linux-x64.tar.xz    // 下载# tar xf  node-v10.9.0-linux-x64.tar.xz       // 解压# cd node-v10.9.0-linux-x64/                  // 进入解压目录# ./bin/node -v                               // 执行node命令 查看版本v10.9.0

解压文件的 bin 目录底下包含了 node、npm 等命令,我们可以使用 ln 命令来设置软连接:

ln -s /usr/software/nodejs/bin/npm   /usr/local/bin/ ln -s /usr/software/nodejs/bin/node   /usr/local/bin/

Ubuntu 源码安装 Node.js

以下部分我们将介绍在 Ubuntu Linux 下使用源码安装 Node.js 。 其他的 Linux 系统,如 Centos 等类似如下安装步骤。

在 Github 上获取 Node.js 源码:

$ sudo git clone https://github.com/nodejs/node.gitCloning into 'node'...

修改目录权限:

$ sudo chmod -R 755 node

使用 ./configure 创建编译文件,并按照:

$ cd node$ sudo ./configure$ sudo make$ sudo make install

查看 node 版本:

$ node --versionv0.10.25

Ubuntu apt-get命令安装

命令格式如下:

sudo apt-get install nodejssudo apt-get install npm

CentOS 下源码安装 Node.js

1、下载源码,你需要在https://nodejs.org/en/download/下载最新的Nodejs版本,本文以v0.10.24为例:

cd /usr/local/src/wget http://nodejs.org/dist/v0.10.24/node-v0.10.24.tar.gz

2、解压源码

tar zxvf node-v0.10.24.tar.gz

3、 编译安装

cd node-v0.10.24./configure --prefix=/usr/local/node/0.10.24makemake install

4、 配置NODE_HOME,进入profile编辑环境变量

vim /etc/profile

设置 nodejs 环境变量,在 export PATH USER LOGNAME MAIL HOSTNAME HISTSIZE HISTCONTROL 一行的上面添加如下内容:

#set for nodejsexport NODE_HOME=/usr/local/node/0.10.24export PATH=$NODE_HOME/bin:$PATH

:wq保存并退出,编译/etc/profile 使配置生效

source /etc/profile

验证是否安装配置成功

node -v

输出 v0.10.24 表示配置成功

npm模块安装路径

/usr/local/node/0.10.24/lib/node_modules/

注:Nodejs 官网提供了编译好的 Linux 二进制包,你也可以下载下来直接应用。

Mac OS 上安装

你可以通过以下两种方式在 Mac OS 上来安装 node:

  • 1、在官方下载网站下载 pkg 安装包,直接点击安装即可。
  • 2、使用 brew 命令来安装:brew install node


Node.js 非常强大,只需动手写几行代码就可以构建出整个HTTP服务器。事实上,我们的Web应用以及对应的Web服务器基本上是一样的。

在我们创建Node.js第一个"Hello, World!"应用前,让我们先了解下Node.js应用是由哪几部分组成的:

  1. 引入模块(required):我们可以使用require指令来载入Node.js模块。

  2. 创建服务器:服务器可以监听客户端的请求,类似于Apache 、Nginx等HTTP服务器。

  3. 接收请求与响应请求:服务器很容易创建,客户端可以使用浏览器或终端发送HTTP请求,服务器接收请求后返回响应数据。


创建 Node.js 应用

步骤一、引入required模块

我们使用require指令来载入http模块,并将实例化的HTTP赋值给变量http,实例如下:

var http = require("http");

步骤二、创建服务器

接下来我们使用http.createServer()方法创建服务器,并使用listen方法绑定8888端口。 函数通过request, response参数来接收和响应数据。

实例如下,在你本地计算机中创建一个文件项目,并在这个文件项目中的根目录下创建一个叫server.js的文件,并写入以下代码:

如下项目截图所示:

2

server.js的文件代码如下:

var http = require('http');http.createServer(function (request, response) {	// 发送 HTTP 头部 	// HTTP 状态值: 200 : OK	// 内容类型: text/plain	response.writeHead(200, {'Content-Type': 'text/plain'});	// 发送响应数据 "Hello World"	response.end('Hello World
');}).listen(8888);// 终端打印如下信息console.log('Server running at http://127.0.0.1:8888/');

以上代码我们完成了一个可以工作的HTTP服务器。

在文件中打开Powershell窗口如下所示:

2

之后在执行下面的命令既可:

node server.jsServer running at http://127.0.0.1:8888/

3

接下来,打开浏览器访问http://127.0.0.1:8888/,你会看到一个写着"Hello World"的网页。

1

分析Node.js的HTTP服务器:

  • 第一行请求(require)Node.js自带的 http 模块,并且把它赋值给http变量。
  • 接下来我们调用http模块提供的函数:createServer 。这个函数会返回 一个对象,这个对象有一个叫做listen的方法,这个方法有一个数值参数,指定这个HTTP服务器监听的端口号。

Gif 实例演示

接下来我们通过Gif图为大家演示实例操作:

1


本文介绍了 Node.js 中 NPM 的使用,我们先来了解什么是 NPM。

NPM是随同NodeJS一起安装的包管理工具,能解决NodeJS代码部署上的很多问题,常见的使用场景有以下几种:

  • 允许用户从NPM服务器下载别人编写的第三方包到本地使用。
  • 允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用。
  • 允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用。

由于新版的nodejs已经集成了npm,所以之前npm也一并安装好了。同样可以通过输入npm -v来测试是否成功安装。命令如下,出现版本提示表示安装成功:

$ npm -v2.3.0

如果你安装的是旧版本的 npm,可以很容易得通过 npm 命令来升级,命令如下:

$ sudo npm install npm -g/usr/local/bin/npm -> /usr/local/lib/node_modules/npm/bin/npm-cli.jsnpm@2.14.2 /usr/local/lib/node_modules/npm

如果是 Window 系统使用以下命令即可:

npm install npm -g
使用淘宝镜像的命令:
npm install -g cnpm --registry=https://registry.npm.taobao.org

使用 npm 命令安装模块

npm 安装 Node.js 模块语法格式如下:

$ npm install <Module Name>

以下实例,我们使用 npm 命令安装常用的 Node.js web框架模块 express:

$ npm install express

安装好之后,express 包就放在了工程目录下的 node_modules 目录中,因此在代码中只需要通过 require('express') 的方式就好,无需指定第三方包路径。

var express = require('express');

全局安装与本地安装

npm 的包安装分为本地安装(local)、全局安装(global)两种,从敲的命令行来看,差别只是有没有-g而已,比如

npm install express          # 本地安装npm install express -g   # 全局安装

如果出现以下错误:

npm err! Error: connect ECONNREFUSED 127.0.0.1:8087 

解决办法为:

$ npm config set proxy null

本地安装

  • 1. 将安装包放在 ./node_modules 下(运行 npm 命令时所在的目录),如果没有 node_modules 目录,会在当前执行 npm 命令的目录下生成 node_modules 目录。
  • 2. 可以通过 require() 来引入本地安装的包。

全局安装

  • 1. 将安装包放在 /usr/local 下或者你 node 的安装目录。
  • 2. 可以直接在命令行里使用。

如果你希望具备两者功能,则需要在两个地方安装它或使用 npm link。

接下来我们使用全局方式安装 express

$ npm install express -g

安装过程输出如下内容,第一行输出了模块的版本号及安装位置。

express@4.13.3 node_modules/express├── escape-html@1.0.2├── range-parser@1.0.2├── merge-descriptors@1.0.0├── array-flatten@1.1.1├── cookie@0.1.3├── utils-merge@1.0.0├── parseurl@1.3.0├── cookie-signature@1.0.6├── methods@1.1.1├── fresh@0.3.0├── vary@1.0.1├── path-to-regexp@0.1.7├── content-type@1.0.1├── etag@1.7.0├── serve-static@1.10.0├── content-disposition@0.5.0├── depd@1.0.1├── qs@4.0.0├── finalhandler@0.4.0 (unpipe@1.0.0)├── on-finished@2.3.0 (ee-first@1.1.1)├── proxy-addr@1.0.8 (forwarded@0.1.0, ipaddr.js@1.0.1)├── debug@2.2.0 (ms@0.7.1)├── type-is@1.6.8 (media-typer@0.3.0, mime-types@2.1.6)├── accepts@1.2.12 (negotiator@0.5.3, mime-types@2.1.6)└── send@0.13.0 (destroy@1.0.3, statuses@1.2.1, ms@0.7.1, mime@1.3.4, http-errors@1.3.1)

查看安装信息

你可以使用以下命令来查看所有全局安装的模块:

$ npm list -g├─┬ cnpm@4.3.2│ ├── auto-correct@1.0.0│ ├── bagpipe@0.3.5│ ├── colors@1.1.2│ ├─┬ commander@2.9.0│ │ └── graceful-readlink@1.0.1│ ├─┬ cross-spawn@0.2.9│ │ └── lru-cache@2.7.3……

如果要查看某个模块的版本号,可以使用命令如下:

$ npm list gruntprojectName@projectVersion /path/to/project/folder└── grunt@0.4.1

使用 package.json

package.json 位于模块的目录下,用于定义包的属性。接下来让我们来看下 express 包的 package.json 文件,位于 node_modules/express/package.json 内容:

{  "name": "express",  "description": "Fast, unopinionated, minimalist web framework",  "version": "4.13.3",  "author": {    "name": "TJ Holowaychuk",    "email": "tj@vision-media.ca"  },  "contributors": [    {      "name": "Aaron Heckmann",      "email": "aaron.heckmann+github@gmail.com"    },    {      "name": "Ciaran Jessup",      "email": "ciaranj@gmail.com"    },    {      "name": "Douglas Christopher Wilson",      "email": "doug@somethingdoug.com"    },    {      "name": "Guillermo Rauch",      "email": "rauchg@gmail.com"    },    {      "name": "Jonathan Ong",      "email": "me@jongleberry.com"    },    {      "name": "Roman Shtylman",      "email": "shtylman+expressjs@gmail.com"    },    {      "name": "Young Jae Sim",      "email": "hanul@hanul.me"    }  ],  "license": "MIT",  "repository": {    "type": "git",    "url": "git+https://github.com/strongloop/express.git"  },  "homepage": "http://expressjs.com/",  "keywords": [    "express",    "framework",    "sinatra",    "web",    "rest",    "restful",    "router",    "app",    "api"  ],  "dependencies": {    "accepts": "~1.2.12",    "array-flatten": "1.1.1",    "content-disposition": "0.5.0",    "content-type": "~1.0.1",    "cookie": "0.1.3",    "cookie-signature": "1.0.6",    "debug": "~2.2.0",    "depd": "~1.0.1",    "escape-html": "1.0.2",    "etag": "~1.7.0",    "finalhandler": "0.4.0",    "fresh": "0.3.0",    "merge-descriptors": "1.0.0",    "methods": "~1.1.1",    "on-finished": "~2.3.0",    "parseurl": "~1.3.0",    "path-to-regexp": "0.1.7",    "proxy-addr": "~1.0.8",    "qs": "4.0.0",    "range-parser": "~1.0.2",    "send": "0.13.0",    "serve-static": "~1.10.0",    "type-is": "~1.6.6",    "utils-merge": "1.0.0",    "vary": "~1.0.1"  },  "devDependencies": {    "after": "0.8.1",    "ejs": "2.3.3",    "istanbul": "0.3.17",    "marked": "0.3.5",    "mocha": "2.2.5",    "should": "7.0.2",    "supertest": "1.0.1",    "body-parser": "~1.13.3",    "connect-redis": "~2.4.1",    "cookie-parser": "~1.3.5",    "cookie-session": "~1.2.0",    "express-session": "~1.11.3",    "jade": "~1.11.0",    "method-override": "~2.3.5",    "morgan": "~1.6.1",    "multiparty": "~4.1.2",    "vhost": "~3.0.1"  },  "engines": {    "node": ">= 0.10.0"  },  "files": [    "LICENSE",    "History.md",    "Readme.md",    "index.js",    "lib/"  ],  "scripts": {    "test": "mocha --require test/support/env --reporter spec --bail --check-leaks test/ test/acceptance/",    "test-ci": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --require test/support/env --reporter spec --check-leaks test/ test/acceptance/",    "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require test/support/env --reporter dot --check-leaks test/ test/acceptance/",    "test-tap": "mocha --require test/support/env --reporter tap --check-leaks test/ test/acceptance/"  },  "gitHead": "ef7ad681b245fba023843ce94f6bcb8e275bbb8e",  "bugs": {    "url": "https://github.com/strongloop/express/issues"  },  "_id": "express@4.13.3",  "_shasum": "ddb2f1fb4502bf33598d2b032b037960ca6c80a3",  "_from": "express@*",  "_npmVersion": "1.4.28",  "_npmUser": {    "name": "dougwilson",    "email": "doug@somethingdoug.com"  },  "maintainers": [    {      "name": "tjholowaychuk",      "email": "tj@vision-media.ca"    },    {      "name": "jongleberry",      "email": "jonathanrichardong@gmail.com"    },    {      "name": "dougwilson",      "email": "doug@somethingdoug.com"    },    {      "name": "rfeng",      "email": "enjoyjava@gmail.com"    },    {      "name": "aredridel",      "email": "aredridel@dinhe.net"    },    {      "name": "strongloop",      "email": "callback@strongloop.com"    },    {      "name": "defunctzombie",      "email": "shtylman@gmail.com"    }  ],  "dist": {    "shasum": "ddb2f1fb4502bf33598d2b032b037960ca6c80a3",    "tarball": "http://registry.npmjs.org/express/-/express-4.13.3.tgz"  },  "directories": {},  "_resolved": "https://registry.npmjs.org/express/-/express-4.13.3.tgz",  "readme": "ERROR: No README data found!"}

Package.json 属性说明

  • name - 包名。
  • version - 包的版本号。
  • description - 包的描述。
  • homepage - 包的官网 url 。
  • author - 包的作者姓名。
  • contributors - 包的其他贡献者姓名。
  • dependencies - 依赖包列表。如果依赖包没有安装,npm 会自动将依赖包安装在 node_module 目录下。
  • repository - 包代码存放的地方的类型,可以是 git 或 svn,git 可在 Github 上。
  • main - main 字段指定了程序的主入口文件,require('moduleName') 就会加载这个文件。这个字段的默认值是模块根目录下面的 index.js。
  • keywords - 关键字

卸载模块

我们可以使用以下命令来卸载 Node.js 模块。

$ npm uninstall express

卸载后,你可以到 /node_modules/ 目录下查看包是否还存在,或者使用以下命令查看:

$ npm ls

更新模块

我们可以使用以下命令更新模块:

$ npm update express

搜索模块

使用以下来搜索模块:

$ npm search express

创建模块

创建模块,package.json 文件是必不可少的。我们可以使用 NPM 生成 package.json 文件,生成的文件包含了基本的结果。

$ npm initThis utility will walk you through creating a package.json file.It only covers the most common items, and tries to guess sensible defaults.See `npm help json` for definitive documentation on these fieldsand exactly what they do.Use `npm install <pkg> --save` afterwards to install a package andsave it as a dependency in the package.json file.Press ^C at any time to quit.name: (node_modules) 51coolma                   # 模块名version: (1.0.0) description: Node.js 测试模块(www.51coolma.cn)  # 描述
entry point: (index.js) test command: make testgit repository: # Github 地址keywords: author: license: (ISC) About to write to ……/node_modules/package.json: # 生成地址{ "name": "51coolma", "version": "1.0.0", "description": "Node.js 测试模块(www.51coolma.cn)", ……}Is this ok? (yes) yes

以上的信息,你需要根据你自己的情况输入。在最后输入 "yes" 后会生成 package.json 文件。

接下来我们可以使用以下命令在 npm 资源库中注册用户(使用邮箱注册):

$ npm adduserUsername: mcmohdPassword:Email: (this IS public) mcmohd@gmail.com

接下来我们就用以下命令来发布模块:

$ npm publish

如果你以上的步骤都操作正确,你就可以跟其他模块一样使用 npm 来安装。

版本号

使用NPM下载和发布代码时都会接触到版本号。NPM使用语义版本号来管理代码,这里简单介绍一下。

语义版本号分为X.Y.Z三位,分别代表主版本号、次版本号和补丁版本号。当代码变更时,版本号按以下原则更新。

  • 如果只是修复bug,需要更新Z位。
  • 如果是新增了功能,但是向下兼容,需要更新Y位。
  • 如果有大变动,向下不兼容,需要更新X位。

版本号有了这个保证后,在申明第三方包依赖时,除了可依赖于一个固定版本号外,还可依赖于某个范围的版本号。例如"argv": "0.0.x"表示依赖于0.0.x系列的最新版argv。

NPM支持的所有版本号范围指定方式可以查看官方文档

NPM 常用命令

除了本章介绍的部分外,NPM还提供了很多功能,package.json里也有很多其它有用的字段。

除了可以在npmjs.org/doc/查看官方文档外,这里再介绍一些NPM常用命令。

NPM提供了很多命令,例如install和publish,使用npm help可查看所有命令。

  • NPM提供了很多命令,例如install和publish,使用npm help可查看所有命令。
  • 使用npm help <command>可查看某条命令的详细帮助,例如npm help install。
  • 在package.json所在目录下使用npm install . -g可先在本地安装当前命令行程序,可用于发布前的本地测试。
  • 使用npm update <package>可以把当前目录下node_modules子目录里边的对应模块更新至最新版本。
  • 使用npm update <package> -g可以把全局安装的对应命令行程序更新至最新版。
  • 使用npm cache clear可以清空NPM本地缓存,用于对付使用相同版本号发布新版本代码的人。
  • 使用npm unpublish <package>@<version>可以撤销发布自己发布过的某个版本代码。

使用淘宝 NPM 镜像

大家都知道国内直接使用 npm 的官方镜像是非常慢的,这里推荐使用淘宝 NPM 镜像。

淘宝 NPM 镜像是一个完整 npmjs.org 镜像,你可以用此代替官方版本(只读),同步频率目前为 10分钟 一次以保证尽量与官方服务同步。

你可以使用淘宝定制的 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm:

$ npm install -g cnpm --registry=https://registry.npm.taobao.org

这样就可以使用 cnpm 命令来安装模块了:

$ cnpm install [name]

更多信息可以查阅:http://npm.taobao.org/


Node.js REPL(Read Eval Print Loop:交互式解释器) 表示一个电脑的环境,类似 Window 系统的终端或 Unix/Linux shell,我们可以在终端中输入命令,并接收系统的响应。

REPL 的交互式的编程环境可以实时的验证你所编写的代码,非常适合于验证 Node.js 和 JavaScript 的相关 API。

Node 自带了交互式解释器,可以执行以下任务:

  • 读取 - 读取用户输入,解析输入了Javascript 数据结构并存储在内存中。

  • 执行 - 执行输入的数据结构

  • 打印 - 输出结果

  • 循环 - 循环操作以上步骤直到用户两次按下 ctrl-c 按钮退出。

Node 的交互式解释器可以很好的调试 Javascript 代码。

开始学习 REPL

我们可以输入以下命令来启动 Node 的终端:

$ node> 

这时我们就可以在 > 后输入简单的表达式,并按下回车键来计算结果。

简单的表达式运算

接下来让我们在 Node.js REPL 的命令行窗口中执行简单的数学运算:

$ node> 1 +45> 5 / 22.5> 3 * 618> 4 - 13> 1 + ( 2 * 3 ) - 43>

使用变量

你可以将数据存储在变量中,并在你需要的使用它。

变量声明需要使用 var 关键字,如果没有使用 var 关键字变量会直接打印出来。

使用 var 关键字的变量可以使用 console.log() 来输出变量。

$ node> x = 1010> var y = 10undefined> x + y20> console.log("Hello World")Hello Worldundefined> console.log("www.51coolma.cn")www.51coolma.cnundefined

多行表达式

Node REPL 支持输入多行表达式,这就有点类似 JavaScript。接下来让我们来执行一个 do-while 循环:

$ node> var x = 0undefined> do {... x++;... console.log("x: " + x);... } while ( x < 5 );x: 1x: 2x: 3x: 4x: 5undefined>

... 三个点的符号是系统自动生成的,你回车换行后即可。Node 会自动检测是否为连续的表达式。

下划线(_)变量

你可以使用下划线(_)获取表达式的运算结果:

$ node> var x = 10undefined> var y = 20undefined> x + y30> var sum = _undefined> console.log(sum)30undefined>

REPL 命令

  • ctrl + c - 退出当前终端。

  • ctrl + c 按下两次 - 退出 Node REPL。

  • ctrl + d - 退出 Node REPL.

  • 向上/向下 键 - 查看输入的历史命令

  • tab 键 - 列出当前命令

  • .help - 列出使用命令

  • .break - 退出多行表达式

  • .clear - 退出多行表达式

  • .save filename - 保存当前的 Node REPL 会话到指定文件

  • .load filename - 载入当前 Node REPL 会话的文件内容。


停止 REPL

前面我们已经提到按下两次 ctrl + c 建就能退出 REPL:

$ node>(^C again to quit)>

Gif 实例演示

接下来我们通过 Gif 图为大家演示实例操作:

2

相关阅读

JavaScript console对象


Node.js 异步编程的直接体现就是回调。

异步编程依托于回调来实现,但不能说使用了回调后程序就异步化了。

回调函数在完成任务后就会被调用,Node 使用了大量的回调函数,Node 所有 API 都支持回调函数。

例如,我们可以一边读取文件,一边执行其他命令,在文件读取完成后,我们将文件内容作为回调函数的参数返回。这样在执行代码时就没有阻塞或等待文件 I/O 操作。这就大大提高了 Node.js 的性能,可以处理大量的并发请求。

回调函数一般作为函数的最后一个参数出现:

function foo1(name, age, callback) { }function foo2(value, callback1, callback2) { }

阻塞代码实例

创建一个文件 input.txt ,内容如下:

W3Cschool教程官网地址:www.51coolma.cn

创建 main.js 文件, 代码如下:

var fs = require("fs");var data = fs.readFileSync('input.txt');console.log(data.toString());console.log("程序执行结束!");

以上代码执行结果如下:

$ node main.jsW3Cschool教程官网地址:www.51coolma.cn程序执行结束!

非阻塞代码实例

创建一个文件 input.txt ,内容如下:

W3Cschool教程官网地址:www.51coolma.cn

创建 main.js 文件, 代码如下:

var fs = require("fs");fs.readFile('input.txt', function (err, data) {    if (err) return console.error(err);    console.log(data.toString());});console.log("程序执行结束!");

以上代码执行结果如下:

$ node main.js程序执行结束!W3Cschool教程官网地址:www.51coolma.cn

以上两个实例我们了解了阻塞与非阻塞调用的不同。第一个实例在文件读取完后才执行完程序。 第二个实例我们呢不需要等待文件读取完,这样就可以在读取文件时同时执行接下来的代码,大大提高了程序的性能。

因此,阻塞按是按顺序执行的,而非阻塞是不需要按顺序的,所以如果需要处理回调函数的参数,我们就需要写在回调函数内。


Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高。

Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发。

Node.js 基本上所有的事件机制都是用设计模式中观察者模式实现。

Node.js 单线程类似进入一个while(true)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数.


事件驱动程序

Node.js 使用事件驱动模型,当web server接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求。

当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户。

这个模型非常高效可扩展性非常强,因为webserver一直接受请求而不等待任何读写操作。(这也被称之为非阻塞式IO或者事件驱动IO)

在事件驱动模型中,会生成一个主循环来监听事件,当检测到事件时触发回调函数。

整个事件驱动的流程就是这么实现的,非常简洁。有点类似于观察者模式,事件相当于一个主题(Subject),而所有注册到这个事件上的处理函数相当于观察者(Observer)。

Node.js 有多个内置的事件,我们可以通过引入 events 模块,并通过实例化 EventEmitter 类来绑定和监听事件,如下实例:

// 引入 events 模块var events = require('events');// 创建 eventEmitter 对象var eventEmitter = new events.EventEmitter();

以下程序绑定事件处理程序:

// 绑定事件及事件的处理程序eventEmitter.on('eventName', eventHandler);

我们可以通过程序触发事件:

// 触发事件eventEmitter.emit('eventName');

实例

创建 main.js 文件,代码如下所示:

// 引入 events 模块var events = require('events');// 创建 eventEmitter 对象var eventEmitter = new events.EventEmitter();// 创建事件处理程序var connectHandler = function connected() {   console.log('连接成功。');     // 触发 data_received 事件    eventEmitter.emit('data_received');}// 绑定 connection 事件处理程序eventEmitter.on('connection', connectHandler); // 使用匿名函数绑定 data_received 事件eventEmitter.on('data_received', function(){   console.log('数据接收成功。');});// 触发 connection 事件 eventEmitter.emit('connection');console.log("程序执行完毕。");

接下来让我们执行以上代码:

$ node main.js连接成功。数据接收成功。程序执行完毕。

Node 应用程序是如何工作的?

在 Node 应用程序中,执行异步操作的函数将回调函数作为最后一个参数, 回调函数接收错误对象作为第一个参数。

接下来让我们来重新看下前面的实例,创建一个 input.txt ,文件内容如下:

W3Cschool教程官网地址:www.51coolma.cn

创建 main.js 文件,代码如下:

var fs = require("fs");fs.readFile('input.txt', function (err, data) {   if (err){      console.log(err.stack);      return;   }   console.log(data.toString());});console.log("程序执行完毕");

以上程序中 fs.readFile() 是异步函数用于读取文件。 如果在读取文件过程中发生错误,错误 err 对象就会输出错误信息。

如果没发生错误,readFile 跳过 err 对象的输出,文件内容就通过回调函数输出。

执行以上代码,执行结果如下:

程序执行完毕W3Cschool教程官网地址:www.51coolma.cn

接下来我们删除 input.txt 文件,执行结果如下所示:

程序执行完毕Error: ENOENT, open 'input.txt'

因为文件 input.txt 不存在,所以输出了错误信息。


Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列。

Node.js 里面的许多对象都会分发事件:一个net.Server对象会在每次有新连接时分发一个事件, 一个fs.readStream对象会在文件被打开的时候发出一个事件。 所有这些产生事件的对象都是 events.EventEmitter 的实例。 


EventEmitter 类

events 模块只提供了一个对象: events.EventEmitter。EventEmitter 的核心就是事件触发与事件监听器功能的封装。

你可以通过require("events");来访问该模块。

// 引入 events 模块var events = require('events');// 创建 eventEmitter 对象var eventEmitter = new events.EventEmitter();

EventEmitter 对象如果在实例化时发生错误,会触发 error 事件。当添加新的监听器时,newListener 事件会触发,当监听器被移除时,removeListener 事件被触发。

下面我们用一个简单的例子说明 EventEmitter 的用法:

//event.js 文件var EventEmitter = require('events').EventEmitter; var event = new EventEmitter(); event.on('some_event', function() {     console.log('some_event 事件触发'); }); setTimeout(function() {     event.emit('some_event'); }, 1000); 

执行结果如下:

运行这段代码,1 秒后控制台输出了 'some_event 事件触发'。其原理是 event 对象注册了事件 some_event 的一个监听器,然后我们通过 setTimeout 在 1000 毫秒以后向 event 对象发送事件 some_event,此时会调用some_event 的监听器。

$ node event.js some_event 事件触发

EventEmitter 的每个事件由一个事件名和若干个参数组成,事件名是一个字符串,通常表达一定的语义。对于每个事件,EventEmitter 支持 若干个事件监听器。

当事件触发时,注册到这个事件的事件监听器被依次调用,事件参数作为回调函数参数传递。

让我们以下面的例子解释这个过程:

//event.js 文件var events = require('events'); var emitter = new events.EventEmitter(); emitter.on('someEvent', function(arg1, arg2) {     console.log('listener1', arg1, arg2); }); emitter.on('someEvent', function(arg1, arg2) {     console.log('listener2', arg1, arg2); }); emitter.emit('someEvent', 'arg1 参数', 'arg2 参数'); 

执行以上代码,运行的结果如下:

$ node event.js listener1 arg1 参数 arg2 参数listener2 arg1 参数 arg2 参数

以上例子中,emitter 为事件 someEvent 注册了两个事件监听器,然后触发了 someEvent 事件。

运行结果中可以看到两个事件监听器回调函数被先后调用。 这就是EventEmitter最简单的用法。

EventEmitter 提供了多个属性,如 on 和 emit。on 函数用于绑定事件函数,emit 属性用于触发一个事件。接下来我们来具体看下 EventEmitter 的属性介绍。

方法

序号方法 & 描述
1addListener(event, listener)
为指定事件添加一个监听器到监听器数组的尾部。
2on(event, listener)
为指定事件注册一个监听器,接受一个字符串 event 和一个回调函数。
server.on('connection', function (stream) {  console.log('someone connected!');});
3once(event, listener)
为指定事件注册一个单次监听器,即 监听器最多只会触发一次,触发后立刻解除该监听器。
server.once('connection', function (stream) {  console.log('Ah, we have our first user!');});
4removeListener(event, listener)

移除指定事件的某个监听器,监听器必须是该事件已经注册过的监听器。

它接受两个参数,第一个是事件名称,第二个是回调函数名称。

var callback = function(stream) {  console.log('someone connected!');};server.on('connection', callback);// ...server.removeListener('connection', callback);
5removeAllListeners([event])
移除所有事件的所有监听器, 如果指定事件,则移除指定事件的所有监听器。
6setMaxListeners(n)
默认情况下, EventEmitters 如果你添加的监听器超过 10 个就会输出警告信息。 setMaxListeners 函数用于改变监听器的默认限制的数量。
7listeners(event)
返回指定事件的监听器数组。
8emit(event, [arg1], [arg2], [...])
按监听器的顺序执行执行每个监听器,如果事件有注册监听返回 true,否则返回 false。

类方法

序号方法 & 描述
1listenerCount(emitter, event)
返回指定事件的监听器数量。
events.EventEmitter.listenerCount(emitter, eventName) //已废弃,不推荐events.emitter.listenerCount(eventName) //推荐

事件

序号事件 & 描述
1newListener
  • event - 字符串,事件名称

  • listener - 处理事件函数

该事件在添加新监听器时被触发。

2removeListener
  • event - 字符串,事件名称

  • listener - 处理事件函数

从指定监听器数组中删除一个监听器。需要注意的是,此操作将会改变处于被删监听器之后的那些监听器的索引。

实例

以下实例通过 connection(连接)事件演示了 EventEmitter 类的应用。

创建 main.js 文件,代码如下:

var events = require('events');var eventEmitter = new events.EventEmitter();// 监听器 #1var listener1 = function listener1() {   console.log('监听器 listener1 执行。');}// 监听器 #2var listener2 = function listener2() {  console.log('监听器 listener2 执行。');}// 绑定 connection 事件,处理函数为 listener1 eventEmitter.addListener('connection', listener1);// 绑定 connection 事件,处理函数为 listener2eventEmitter.on('connection', listener2);var eventListeners = eventEmitter.listenerCount('connection');console.log(eventListeners + " 个监听器监听连接事件。");// 处理 connection 事件 eventEmitter.emit('connection');// 移除监绑定的 listener1 函数eventEmitter.removeListener('connection', listener1);console.log("listener1 不再受监听。");// 触发连接事件eventEmitter.emit('connection');eventListeners = eventEmitter.listenerCount('connection');console.log(eventListeners + " 个监听器监听连接事件。");console.log("程序执行完毕。");

以上代码,执行结果如下所示:

$ node main.js2 个监听器监听连接事件。监听器 listener1 执行。监听器 listener2 执行。listener1 不再受监听。监听器 listener2 执行。1 个监听器监听连接事件。程序执行完毕。

error 事件

EventEmitter 定义了一个特殊的事件 error,它包含了错误的语义,我们在遇到 异常的时候通常会触发 error 事件。

当 error 被触发时,EventEmitter 规定如果没有响 应的监听器,Node.js 会把它当作异常,退出程序并输出错误信息。

我们一般要为会触发 error 事件的对象设置监听器,避免遇到错误后整个程序崩溃。例如:

var events = require('events'); var emitter = new events.EventEmitter(); emitter.emit('error'); 

运行时会显示以下错误:

node.js:201 throw e; // process.nextTick error, or 'error' event on first tick ^ Error: Uncaught, unspecified 'error' event. at EventEmitter.emit (events.js:50:15) at Object.<anonymous> (/home/byvoid/error.js:5:9) 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) 

继承 EventEmitter

大多数时候我们不会直接使用 EventEmitter,而是在对象中继承它。包括 fs、net、 http 在内的,只要是支持事件响应的核心模块都是 EventEmitter 的子类。

为什么要这样做呢?原因有两点:

首先,具有某个实体功能的对象实现事件符合语义, 事件的监听和发生应该是一个对象的方法。

其次 JavaScript 的对象机制是基于原型的,支持 部分多重继承,继承 EventEmitter 不会打乱对象原有的继承关系。


JavaScript 语言自身只有字符串数据类型,没有二进制数据类型。

但在处理像TCP流或文件流时,必须使用到二进制数据。因此在 Node.js中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。

在 Node.js 中,Buffer 类是随 Node 内核一起发布的核心库。Buffer 库为 Node.js 带来了一种存储原始数据的方法,可以让 Node.js 处理二进制数据,每当需要在 Node.js 中处理I/O操作中移动的数据时,就有可能使用 Buffer 库。原始数据存储在 Buffer 类的实例中。一个 Buffer 类似于一个整数数组,但它对应于 V8 堆内存之外的一块原始内存。

 在v6.0之前创建Buffer对象直接使用new Buffer()构造函数来创建对象实例,但是Buffer对内存的权限操作相比很大,可以直接捕获一些敏感信息,所以在v6.0以后,官方文档里面建议使用 Buffer.from() 接口去创建Buffer对象。


Buffer 与字符编码

Buffer 实例一般用于表示编码字符的序列,比如 UTF-8 、 UCS2 、 Base64 、或十六进制编码的数据。 通过使用显式的字符编码,就可以在 Buffer 实例与普通的 JavaScript 字符串之间进行相互转换。

const buf = Buffer.from('51coolma', 'ascii');
// 输出 72756e6f6f62console.log(buf.toString('hex'));// 输出 cnVub29iconsole.log(buf.toString('base64'));

Node.js 目前支持的字符编码包括:

  • ascii - 仅支持 7 位 ASCII 数据。如果设置去掉高位的话,这种编码是非常快的。
  • utf8 - 多字节编码的 Unicode 字符。许多网页和其他文档格式都使用 UTF-8 。
  • utf16le - 2 或 4 个字节,小字节序编码的 Unicode 字符。支持代理对(U+10000 至 U+10FFFF)。
  • ucs2 - utf16le 的别名。
  • base64 - Base64 编码。
  • latin1 - 一种把 Buffer 编码成一字节编码的字符串的方式。
  • binary - latin1 的别名。
  • hex - 将每个字节编码为两个十六进制字符。

创建 Buffer 类

Buffer 提供了以下 API 来创建 Buffer 类:

  • Buffer.alloc(size[, fill[, encoding]]): 返回一个指定大小的 Buffer 实例,如果没有设置 fill,则默认填满 0
  • Buffer.allocUnsafe(size): 返回一个指定大小的 Buffer 实例,但是它不会被初始化,所以它可能包含敏感的数据
  • Buffer.allocUnsafeSlow(size)
  • Buffer.from(array): 返回一个被 array 的值初始化的新的 Buffer 实例(传入的 array 的元素只能是数字,不然就会自动被 0 覆盖)
  • Buffer.from(arrayBuffer[, byteOffset[, length]]): 返回一个新建的与给定的 ArrayBuffer 共享同一内存的 Buffer。
  • Buffer.from(buffer): 复制传入的 Buffer 实例的数据,并返回一个新的 Buffer 实例
  • Buffer.from(string[, encoding]): 返回一个被 string 的值初始化的新的 Buffer 实例
// 创建一个长度为 10、且用 0 填充的 Buffer。const buf1 = Buffer.alloc(10);// 创建一个长度为 10、且用 0x1 填充的 Buffer。 const buf2 = Buffer.alloc(10, 1);// 创建一个长度为 10、且未初始化的 Buffer。// 这个方法比调用 Buffer.alloc() 更快,// 但返回的 Buffer 实例可能包含旧数据,// 因此需要使用 fill() 或 write() 重写。const buf3 = Buffer.allocUnsafe(10);// 创建一个包含 [0x1, 0x2, 0x3] 的 Buffer。const buf4 = Buffer.from([1, 2, 3]);// 创建一个包含 UTF-8 字节 [0x74, 0xc3, 0xa9, 0x73, 0x74] 的 Buffer。const buf5 = Buffer.from('tést');// 创建一个包含 Latin-1 字节 [0x74, 0xe9, 0x73, 0x74] 的 Buffer。const buf6 = Buffer.from('tést', 'latin1');

写入缓冲区

语法

写入 Node 缓冲区的语法如下所示:

buf.write(string[, offset[, length]][, encoding])

参数

参数描述如下:

  • string - 写入缓冲区的字符串。
  • offset - 缓冲区开始写入的索引值,默认为 0 。
  • length - 写入的字节数,默认为 buffer.length
  • encoding - 使用的编码。默认为 'utf8' 。

根据 encoding 的字符编码写入 string 到 buf 中的 offset 位置。 length 参数是写入的字节数。 如果 buf 没有足够的空间保存整个字符串,则只会写入 string 的一部分。 只部分解码的字符不会被写入。

返回值

返回实际写入的大小。如果 buffer 空间不足, 则只会写入部分字符串。

实例

buf = Buffer.alloc(256);len = buf.write("www.51coolma.cn");
console.log("写入字节数 : "+ len);

执行以上代码,输出结果为:

$node main.js写入字节数 : 16

从缓冲区读取数据

语法

读取 Node 缓冲区数据的语法如下所示:

buf.toString([encoding[, start[, end]]])

参数

参数描述如下:

  • encoding - 使用的编码。默认为 'utf8' 。
  • start - 指定开始读取的索引位置,默认为 0。
  • end - 结束位置,默认为缓冲区的末尾。

返回值

解码缓冲区数据并使用指定的编码返回字符串。

实例

buf = Buffer.alloc(26);for (var i = 0 ; i < 26 ; i++) {  buf[i] = i + 97;}console.log( buf.toString('ascii'));       // 输出: abcdefghijklmnopqrstuvwxyzconsole.log( buf.toString('ascii',0,5));   //使用 'ascii' 编码, 并输出: abcdeconsole.log( buf.toString('utf8',0,5));    // 使用 'utf8' 编码, 并输出: abcdeconsole.log( buf.toString(undefined,0,5)); // 使用默认的 'utf8' 编码, 并输出: abcde

执行以上代码,输出结果为:

$ node main.jsabcdefghijklmnopqrstuvwxyzabcdeabcdeabcde

将 Buffer 转换为 JSON 对象

语法

将 Node Buffer 转换为 JSON 对象的函数语法格式如下:

buf.toJSON()

当字符串化一个 Buffer 实例时,JSON.stringify() 会隐式地调用该 toJSON()。

返回值

返回 JSON 对象。

实例

const buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5]);const json = JSON.stringify(buf);// 输出: {"type":"Buffer","data":[1,2,3,4,5]}console.log(json);const copy = JSON.parse(json, (key, value) => {  return value && value.type === 'Buffer' ?    Buffer.from(value.data) :    value;});// 输出: <Buffer 01 02 03 04 05>console.log(copy);

执行以上代码,输出结果为:

{"type":"Buffer","data":[1,2,3,4,5]}<Buffer 01 02 03 04 05>

缓冲区合并

语法

Node 缓冲区合并的语法如下所示:

Buffer.concat(list[, totalLength])

参数

参数描述如下:

  • list - 用于合并的 Buffer 对象数组列表。
  • totalLength - 指定合并后Buffer对象的总长度。

返回值

返回一个多个成员合并的新 Buffer 对象。

实例

var buffer1 = Buffer.from(('51coolma编程狮'));
var buffer2 = Buffer.from(('www.51coolma.cn'));
var buffer3 = Buffer.concat([buffer1,buffer2]);console.log("buffer3 内容: " + buffer3.toString());

执行以上代码,输出结果为:

buffer3 内容: 编程狮www.51coolma.cn

缓冲区比较

语法

Node Buffer 比较的函数语法如下所示, 该方法在 Node.js v0.12.2 版本引入:

buf.compare(otherBuffer);

参数

参数描述如下:

  • otherBuffer - 与 buf 对象比较的另外一个 Buffer 对象。

返回值

返回一个数字,表示 buf 在 otherBuffer 之前,之后或相同。

实例

var buffer1 = Buffer.from('ABC');var buffer2 = Buffer.from('ABCD');var result = buffer1.compare(buffer2);if(result < 0) {   console.log(buffer1 + " 在 " + buffer2 + "之前");}else if(result == 0){   console.log(buffer1 + " 与 " + buffer2 + "相同");}else {   console.log(buffer1 + " 在 " + buffer2 + "之后");}

执行以上代码,输出结果为:

ABC在ABCD之前

拷贝缓冲区

语法

Node 缓冲区拷贝语法如下所示:

buf.copy(targetBuffer[, targetStart[, sourceStart[, sourceEnd]]])

参数

参数描述如下:

  • targetBuffer - 要拷贝的 Buffer 对象。
  • targetStart - 数字, 可选, 默认: 0
  • sourceStart - 数字, 可选, 默认: 0
  • sourceEnd - 数字, 可选, 默认: buffer.length

返回值

没有返回值。

实例

var buf1 = Buffer.from('abcdefghijkl');var buf2 = Buffer.from('W3CSCHOOL');//将 buf2 插入到 buf1 指定位置上buf2.copy(buf1, 2);console.log(buf1.toString());

执行以上代码,输出结果为:

abW3CSCHOOLijkl

缓冲区裁剪

Node 缓冲区裁剪语法如下所示:

buf.slice([start[, end]])

参数

参数描述如下:

  • start - 数字, 可选, 默认: 0
  • end - 数字, 可选, 默认: buffer.length

返回值

返回一个新的缓冲区,它和旧缓冲区指向同一块内存,但是从索引 start 到 end 的位置剪切。

实例

var buffer1 = Buffer.from('51coolma');// 剪切缓冲区var buffer2 = buffer1.slice(0,2);console.log("buffer2 content: " + buffer2.toString());

执行以上代码,输出结果为:

buffer2 content: w3

缓冲区长度

语法

Node 缓冲区长度计算语法如下所示:

buf.length;

返回值

返回 Buffer 对象所占据的内存长度。

实例

var buffer = Buffer.from('www.51coolma.cn');
// 缓冲区长度console.log("buffer length: " + buffer.length);

执行以上代码,输出结果为:

buffer length: 16

方法参考手册

以下列出了 Node.js Buffer 模块常用的方法(注意有些方法在旧版本是没有的):

序号方法 & 描述
1new Buffer(size)
分配一个新的 size 大小单位为8位字节的 buffer。 注意, size 必须小于 kMaxLength,否则,将会抛出异常 RangeError。废弃的: 使用 Buffer.alloc() 代替(或 Buffer.allocUnsafe())。
2new Buffer(buffer)
拷贝参数 buffer 的数据到 Buffer 实例。废弃的: 使用 Buffer.from(buffer) 代替。
3new Buffer(str[, encoding])
分配一个新的 buffer ,其中包含着传入的 str 字符串。 encoding 编码方式默认为 'utf8'。 废弃的: 使用 Buffer.from(string[, encoding]) 代替。
4buf.length
返回这个 buffer 的 bytes 数。注意这未必是 buffer 里面内容的大小。length 是 buffer 对象所分配的内存数,它不会随着这个 buffer 对象内容的改变而改变。
5buf.write(string[, offset[, length]][, encoding])
根据参数 offset 偏移量和指定的 encoding 编码方式,将参数 string 数据写入buffer。 offset 偏移量默认值是 0, encoding 编码方式默认是 utf8。 length 长度是将要写入的字符串的 bytes 大小。 返回 number 类型,表示写入了多少 8 位字节流。如果 buffer 没有足够的空间来放整个 string,它将只会只写入部分字符串。 length 默认是 buffer.length - offset。 这个方法不会出现写入部分字符。
6buf.writeUIntLE(value, offset, byteLength[, noAssert])
将 value 写入到 buffer 里, 它由 offset 和 byteLength 决定,最高支持 48 位无符号整数,小端对齐,例如:
const buf = Buffer.allocUnsafe(6);buf.writeUIntLE(0x1234567890ab, 0, 6);// 输出: <Buffer ab 90 78 56 34 12>console.log(buf);
noAssert 值为 true 时,不再验证 value 和 offset 的有效性。 默认是 false。
7buf.writeUIntBE(value, offset, byteLength[, noAssert])
将 value 写入到 buffer 里, 它由 offset 和 byteLength 决定,最高支持 48 位无符号整数,大端对齐。noAssert 值为 true 时,不再验证 value 和 offset 的有效性。 默认是 false。
const buf = Buffer.allocUnsafe(6);buf.writeUIntBE(0x1234567890ab, 0, 6);// 输出: <Buffer 12 34 56 78 90 ab>console.log(buf);
8buf.writeIntLE(value, offset, byteLength[, noAssert])
将value 写入到 buffer 里, 它由offset 和 byteLength 决定,最高支持48位有符号整数,小端对齐。noAssert 值为 true 时,不再验证 value 和 offset 的有效性。 默认是 false。
9buf.writeIntBE(value, offset, byteLength[, noAssert])
将value 写入到 buffer 里, 它由offset 和 byteLength 决定,最高支持48位有符号整数,大端对齐。noAssert 值为 true 时,不再验证 value 和 offset 的有效性。 默认是 false。
10buf.readUIntLE(offset, byteLength[, noAssert])
支持读取 48 位以下的无符号数字,小端对齐。noAssert 值为 true 时, offset 不再验证是否超过 buffer 的长度,默认为 false。
11buf.readUIntBE(offset, byteLength[, noAssert])
支持读取 48 位以下的无符号数字,大端对齐。noAssert 值为 true 时, offset 不再验证是否超过 buffer 的长度,默认为 false。
12buf.readIntLE(offset, byteLength[, noAssert])
支持读取 48 位以下的有符号数字,小端对齐。noAssert 值为 true 时, offset 不再验证是否超过 buffer 的长度,默认为 false。
13buf.readIntBE(offset, byteLength[, noAssert])
支持读取 48 位以下的有符号数字,大端对齐。noAssert 值为 true 时, offset 不再验证是否超过 buffer 的长度,默认为 false。
14buf.toString([encoding[, start[, end]]])
根据 encoding 参数(默认是 'utf8')返回一个解码过的 string 类型。还会根据传入的参数 start (默认是 0) 和 end (默认是 buffer.length)作为取值范围。
15buf.toJSON()
将 Buffer 实例转换为 JSON 对象。
16buf[index]
获取或设置指定的字节。返回值代表一个字节,所以返回值的合法范围是十六进制0x00到0xFF 或者十进制0至 255。
17buf.equals(otherBuffer)
比较两个缓冲区是否相等,如果是返回 true,否则返回 false。
18buf.compare(otherBuffer)
比较两个 Buffer 对象,返回一个数字,表示 buf 在 otherBuffer 之前,之后或相同。
19buf.copy(targetBuffer[, targetStart[, sourceStart[, sourceEnd]]])
buffer 拷贝,源和目标可以相同。 targetStart 目标开始偏移和 sourceStart 源开始偏移默认都是 0。 sourceEnd 源结束位置偏移默认是源的长度 buffer.length 。
20buf.slice([start[, end]])
剪切 Buffer 对象,根据 start(默认是 0 ) 和 end (默认是 buffer.length ) 偏移和裁剪了索引。 负的索引是从 buffer 尾部开始计算的。
21buf.readUInt8(offset[, noAssert])
根据指定的偏移量,读取一个无符号 8 位整数。若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 如果这样 offset 可能会超出buffer 的末尾。默认是 false。
22buf.readUInt16LE(offset[, noAssert])
根据指定的偏移量,使用特殊的 endian 字节序格式读取一个无符号 16 位整数。若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出 buffer 的末尾。默认是 false。
23buf.readUInt16BE(offset[, noAssert])
根据指定的偏移量,使用特殊的 endian 字节序格式读取一个无符号 16 位整数,大端对齐。若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出 buffer 的末尾。默认是 false。
24buf.readUInt32LE(offset[, noAssert])
根据指定的偏移量,使用指定的 endian 字节序格式读取一个无符号 32 位整数,小端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer 的末尾。默认是 false。
25buf.readUInt32BE(offset[, noAssert])
根据指定的偏移量,使用指定的 endian 字节序格式读取一个无符号 32 位整数,大端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer 的末尾。默认是 false。
26buf.readInt8(offset[, noAssert])
根据指定的偏移量,读取一个有符号 8 位整数。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出 buffer 的末尾。默认是 false。
27buf.readInt16LE(offset[, noAssert])
根据指定的偏移量,使用特殊的 endian 格式读取一个 有符号 16 位整数,小端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出 buffer 的末尾。默认是 false。
28buf.readInt16BE(offset[, noAssert])
根据指定的偏移量,使用特殊的 endian 格式读取一个 有符号 16 位整数,大端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出 buffer 的末尾。默认是 false。
29buf.readInt32LE(offset[, noAssert])
根据指定的偏移量,使用指定的 endian 字节序格式读取一个有符号 32 位整数,小端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer 的末尾。默认是 false。
30buf.readInt32BE(offset[, noAssert])
根据指定的偏移量,使用指定的 endian 字节序格式读取一个有符号 32 位整数,大端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer 的末尾。默认是 false。
31buf.readFloatLE(offset[, noAssert])
根据指定的偏移量,使用指定的 endian 字节序格式读取一个 32 位双浮点数,小端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer的末尾。默认是 false。
32buf.readFloatBE(offset[, noAssert])
根据指定的偏移量,使用指定的 endian 字节序格式读取一个 32 位双浮点数,大端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer的末尾。默认是 false。
33buf.readDoubleLE(offset[, noAssert])
根据指定的偏移量,使用指定的 endian字节序格式读取一个 64 位双精度数,小端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer 的末尾。默认是 false。
34buf.readDoubleBE(offset[, noAssert])
根据指定的偏移量,使用指定的 endian字节序格式读取一个 64 位双精度数,大端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer 的末尾。默认是 false。
35buf.writeUInt8(value, offset[, noAssert])
根据传入的 offset 偏移量将 value 写入 buffer。注意:value 必须是一个合法的无符号 8 位整数。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出 buffer 的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则不要使用。默认是 false。
36buf.writeUInt16LE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个合法的无符号 16 位整数,小端对齐。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出buffer的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false。
37buf.writeUInt16BE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个合法的无符号 16 位整数,大端对齐。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出buffer的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false。
38buf.writeUInt32LE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式(LITTLE-ENDIAN:小字节序)将 value 写入buffer。注意:value 必须是一个合法的无符号 32 位整数,小端对齐。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着value 可能过大,或者offset可能会超出buffer的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false。
39buf.writeUInt32BE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式(Big-Endian:大字节序)将 value 写入buffer。注意:value 必须是一个合法的有符号 32 位整数。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者offset可能会超出buffer的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false。
40buf.writeInt8(value, offset[, noAssert])
41buf.writeInt16LE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个合法的 signed 16 位整数。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出 buffer 的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false 。
42buf.writeInt16BE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个合法的 signed 16 位整数。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出 buffer 的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false 。
43buf.writeInt32LE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个合法的 signed 32 位整数。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出 buffer 的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false。
44buf.writeInt32BE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个合法的 signed 32 位整数。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出 buffer 的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false。
45buf.writeFloatLE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer 。注意:当 value 不是一个 32 位浮点数类型的值时,结果将是不确定的。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value可能过大,或者 offset 可能会超出 buffer 的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false。
46buf.writeFloatBE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer 。注意:当 value 不是一个 32 位浮点数类型的值时,结果将是不确定的。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value可能过大,或者 offset 可能会超出 buffer 的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false。
47buf.writeDoubleLE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个有效的 64 位double 类型的值。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出 buffer 的末尾从而造成value被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false。
48buf.writeDoubleBE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个有效的 64 位double 类型的值。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出 buffer 的末尾从而造成value被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false。
49buf.fill(value[, offset][, end])
使用指定的 value 来填充这个 buffer。如果没有指定 offset (默认是 0) 并且 end (默认是 buffer.length) ,将会填充整个buffer。


Stream 是 Node.js 中非常重要的一个模块,应用广泛。

Stream 是一个抽象接口,Node 中有很多对象实现了这个接口。例如,对http 服务器发起请求的request 对象就是一个 Stream,还有stdout(标准输出)。

该抽象接口是可读、可写或是既可读又可写的,通过这些接口,我们可以和磁盘文件、套接字、HTTP请求来交互,实现数据从一个地方流动到另一个地方的功能。

Node.js,Stream 有四种流类型:

  • Readable - 可读操作。

  • Writable - 可写操作。

  • Duplex - 可读可写操作.

  • Transform - 操作被写入数据,然后读出结果。

所有的 Stream 对象都是 EventEmitter 的实例。常用的事件有:

  • data - 当有数据可读时触发。

  • end - 没有更多的数据可读时触发。

  • error - 在接收和写入过程中发生错误时触发。

  • finish - 所有数据已被写入到底层系统时触发。

本教程会为大家介绍常用的流操作。


从流中读取数据

创建 input.txt 文件,内容如下:

W3Cschool教程官网地址:www.51coolma.cn

创建 main.js 文件, 代码如下:

var fs = require("fs");var data = '';// 创建可读流var readerStream = fs.createReadStream('input.txt');// 设置编码为 utf8。readerStream.setEncoding('UTF8');// 处理流事件 --> data, end, and errorreaderStream.on('data', function(chunk) {   data += chunk;});readerStream.on('end',function(){   console.log(data);});readerStream.on('error', function(err){   console.log(err.stack);});console.log("程序执行完毕");

以上代码执行结果如下:

程序执行完毕W3Cschool教程官网地址:www.51coolma.cn

写入流

创建 main.js 文件, 代码如下:

var fs = require("fs");var data = 'W3Cschool教程官网地址:www.51coolma.cn';// 创建一个可以写入的流,写入到文件 output.txt 中var writerStream = fs.createWriteStream('output.txt');// 使用 utf8 编码写入数据writerStream.write(data,'UTF8');// 标记文件末尾writerStream.end();// 处理流事件 --> data, end, and errorwriterStream.on('finish', function() {    console.log("写入完成。");});writerStream.on('error', function(err){   console.log(err.stack);});console.log("程序执行完毕");

以上程序会将 data 变量的数据写入到 output.txt 文件中。代码执行结果如下:

$ node main.js 程序执行完毕写入完成。

查看 output.txt 文件的内容:

$ cat output.txt W3Cschool教程官网地址:www.51coolma.cn

管道流

管道提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另外一个流中。

如上面的图片所示,我们把文件比作装水的桶,而水就是文件里的内容,我们用一根管子(pipe)连接两个桶使得水从一个桶流入另一个桶,这样就慢慢的实现了大文件的复制过程。

以下实例我们通过读取一个文件内容并将内容写入到另外一个文件中。

设置 input.txt 文件内容如下:

W3Cschool教程官网地址:www.51coolma.cn管道流操作实例

创建 main.js 文件, 代码如下:

var fs = require("fs");// 创建一个可读流var readerStream = fs.createReadStream('input.txt');// 创建一个可写流var writerStream = fs.createWriteStream('output.txt');// 管道读写操作// 读取 input.txt 文件内容,并将内容写入到 output.txt 文件中readerStream.pipe(writerStream);console.log("程序执行完毕");

代码执行结果如下:

$ node main.js 程序执行完毕

查看 output.txt 文件的内容:

$ cat output.txt W3Cschool教程官网地址:www.51coolma.cn管道流操作实例

链式流

链式是通过连接输出流到另外一个流并创建多个对个流操作链的机制。链式流一般用于管道操作。

接下来我们就是用管道和链式来压缩和解压文件。

创建 compress.js 文件, 代码如下:

var fs = require("fs");var zlib = require('zlib');// 压缩 input.txt 文件为 input.txt.gzfs.createReadStream('input.txt')  .pipe(zlib.createGzip())  .pipe(fs.createWriteStream('input.txt.gz'));  console.log("文件压缩完成。");

代码执行结果如下:

$ node compress.js 文件压缩完成。

执行完以上操作后,我们可以看到当前目录下生成了 input.txt 的压缩文件 input.txt.gz。

接下来,让我们来解压该文件,创建 decompress.js 文件,代码如下:

var fs = require("fs");var zlib = require('zlib');// 解压 input.txt.gz 文件为 input.txtfs.createReadStream('input.txt.gz')  .pipe(zlib.createGunzip())  .pipe(fs.createWriteStream('input.txt'));  console.log("文件解压完成。");

代码执行结果如下:

$ node decompress.js 文件解压完成。


为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统。

模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个 Node.js 文件就是一个模块,这个文件可能是JavaScript 代码、JSON 或者编译过的C/C++ 扩展。

创建模块

在 Node.js 中,创建一个模块非常简单,如下我们创建一个 'main.js' 文件,代码如下:

var hello = require('./hello');hello.world();

以上实例中,代码 require('./hello') 引入了当前目录下的hello.js文件(./ 为当前目录,node.js默认后缀为js)。

Node.js 提供了exports 和 require 两个对象,其中 exports 是模块公开的接口,require 用于从外部获取一个模块的接口,即所获取模块的 exports 对象。

接下来我们就来创建hello.js文件,代码如下:

exports.world = function() {  console.log('Hello World');}

在以上示例中,hello.js 通过 exports 对象把 world 作为模块的访 问接口,在 main.js 中通过 require('./hello') 加载这个模块,然后就可以直接访 问hello.js 中 exports 对象的成员函数了。

有时候我们只是想把一个对象封装到模块中,格式如下:

module.exports = function() {  // ...}

例如:

//hello.js function Hello() {  var name;     this.setName = function(thyName) {        name = thyName;   };    this.sayHello = function() {      console.log('Hello ' + name);   }; }; module.exports = Hello;

这样就可以直接获得这个对象了:

//main.js var Hello = require('./hello'); hello = new Hello(); hello.setName('BYVoid'); hello.sayHello(); 

模块接口的唯一变化是使用 module.exports = Hello 代替了exports.world = function(){}。 在外部引用该模块时,其接口对象就是要输出的 Hello 对象本身,而不是原先的 exports。


服务端的模块放在哪里

也许你已经注意到,我们已经在代码中使用了模块了。像这样:

var http = require("http");...http.createServer(...);

Node.js中自带了一个叫做"http"的模块,我们在我们的代码中请求它并把返回值赋给一个本地变量。

这把我们的本地变量变成了一个拥有所有 http 模块所提供的公共方法的对象。

Node.js 的 require方法中的文件查找策略如下:

由于Node.js中存在4类模块(原生模块和3种文件模块),尽管require方法极其简单,但是内部的加载却是十分复杂的,其加载优先级也各自不同。如下图所示:

nodejs-require

从文件模块缓存中加载

尽管原生模块与文件模块的优先级不同,但是都不会优先于从文件模块的缓存中加载已经存在的模块。

从原生模块加载

原生模块的优先级仅次于文件模块缓存的优先级。require 方法在解析文件名之后,优先检查模块是否在原生模块列表中。以 http 模块为例,尽管在目录下存在一个http/http.js/http.node/http.json文件,require("http") 都不会从这些文件中加载,而是从原生模块中加载。

原生模块也有一个缓存区,同样也是优先从缓存区加载。如果缓存区没有被加载过,则调用原生模块的加载方式进行加载和执行。

从文件加载

当文件模块缓存中不存在,而且不是原生模块的时候,Node.js会解析require方法传入的参数,并从文件系统中加载实际的文件,加载过程中的包装和编译细节在前一节中已经介绍过,这里我们将详细描述查找文件模块的过程,其中,也有一些细节值得知晓。

require方法接受以下几种参数的传递:

  • http、fs、path等,原生模块。
  • ./mod或../mod,相对路径的文件模块。
  • /pathtomodule/mod,绝对路径的文件模块。
  • mod,非原生模块的文件模块。

在路径 Y 下执行 require(X) 语句执行顺序:

1. 如果 X 是内置模块   a. 返回内置模块   b. 停止执行2. 如果 X 以 '/' 开头   a. 设置 Y 为文件根路径3. 如果 X 以 './' 或 '/' or '../' 开头   a. LOAD_AS_FILE(Y + X)   b. LOAD_AS_DIRECTORY(Y + X)4. LOAD_NODE_MODULES(X, dirname(Y))5. 抛出异常 "not found"LOAD_AS_FILE(X)1. 如果 X 是一个文件, 将 X 作为 JavaScript 文本载入并停止执行。2. 如果 X.js 是一个文件, 将 X.js 作为 JavaScript 文本载入并停止执行。3. 如果 X.json 是一个文件, 解析 X.json 为 JavaScript 对象并停止执行。4. 如果 X.node 是一个文件, 将 X.node 作为二进制插件载入并停止执行。LOAD_INDEX(X)1. 如果 X/index.js 是一个文件,  将 X/index.js 作为 JavaScript 文本载入并停止执行。2. 如果 X/index.json 是一个文件, 解析 X/index.json 为 JavaScript 对象并停止执行。3. 如果 X/index.node 是一个文件,  将 X/index.node 作为二进制插件载入并停止执行。LOAD_AS_DIRECTORY(X)1. 如果 X/package.json 是一个文件,   a. 解析 X/package.json, 并查找 "main" 字段。   b. let M = X + (json main 字段)   c. LOAD_AS_FILE(M)   d. LOAD_INDEX(M)2. LOAD_INDEX(X)LOAD_NODE_MODULES(X, START)1. let DIRS=NODE_MODULES_PATHS(START)2. for each DIR in DIRS:   a. LOAD_AS_FILE(DIR/X)   b. LOAD_AS_DIRECTORY(DIR/X)NODE_MODULES_PATHS(START)1. let PARTS = path split(START)2. let I = count of PARTS - 13. let DIRS = []4. while I >= 0,   a. if PARTS[I] = "node_modules" CONTINUE   b. DIR = path join(PARTS[0 .. I] + "node_modules")   c. DIRS = DIRS + DIR   d. let I = I - 15. return DIRS
exports 和 module.exports 的使用如果要对外暴露属性或方法,就用 exports 就行,要暴露对象(类似class,包含了很多属性和方法),就用 module.exports。


在JavaScript中,一个函数可以作为另一个函数接收一个参数。我们可以先定义一个函数,然后传递,也可以在传递参数的地方直接定义函数。

Node.js中函数的使用与Javascript类似,举例来说,你可以这样做:

function say(word) {  console.log(word);}function execute(someFunction, value) {  someFunction(value);}execute(say, "Hello");

以上代码中,我们把 say 函数作为execute函数的第一个变量进行了传递。这里返回的不是 say 的返回值,而是 say 本身!

这样一来, say 就变成了execute 中的本地变量 someFunction ,execute可以通过调用 someFunction() (带括号的形式)来使用 say 函数。

当然,因为 say 有一个变量, execute 在调用 someFunction 时可以传递这样一个变量。


匿名函数

我们可以把一个函数作为变量传递。但是我们不一定要绕这个"先定义,再传递"的圈子,我们可以直接在另一个函数的括号中定义和传递这个函数:

function execute(someFunction, value) {  someFunction(value);}execute(function(word){ console.log(word) }, "Hello");

我们在 execute 接受第一个参数的地方直接定义了我们准备传递给 execute 的函数。

用这种方式,我们甚至不用给这个函数起名字,这也是为什么它被叫做匿名函数 。


函数传递是如何让HTTP服务器工作的

带着这些知识,我们再来看看我们简约而不简单的HTTP服务器:

var http = require("http");http.createServer(function(request, response) {  response.writeHead(200, {"Content-Type": "text/plain"});  response.write("Hello World");  response.end();}).listen(8888);

现在它看上去应该清晰了很多:我们向 createServer 函数传递了一个匿名函数。

用这样的代码也可以达到同样的目的:

var http = require("http");function onRequest(request, response) {  response.writeHead(200, {"Content-Type": "text/plain"});  response.write("Hello World");  response.end();}http.createServer(onRequest).listen(8888);


我们要为路由提供请求的URL和其他需要的GET及POST参数,随后路由需要根据这些数据来执行相应的代码。

因此,我们需要查看HTTP请求,从中提取出请求的URL以及GET/POST参数。这一功能应当属于路由还是服务器(甚至作为一个模块自身的功能)确实值得探讨,但这里暂定其为我们的HTTP服务器的功能。

我们需要的所有数据都会包含在request对象中,该对象作为onRequest()回调函数的第一个参数传递。但是为了解析这些数据,我们需要额外的Node.JS模块,它们分别是url和querystring模块。

                   url.parse(string).query                                           |           url.parse(string).pathname      |                       |                   |                       |                   |                     ------ -------------------http://localhost:8888/start?foo=bar&hello=world                                ---       -----                                 |          |                                 |          |              querystring(string)["foo"]    |                                            |                         querystring(string)["hello"]

当然我们也可以用querystring模块来解析POST请求体中的参数,稍后会有演示。

现在我们来给onRequest()函数加上一些逻辑,用来找出浏览器请求的URL路径:

var http = require("http");var url = require("url");function start() {  function onRequest(request, response) {    var pathname = url.parse(request.url).pathname;    console.log("Request for " + pathname + " received.");    response.writeHead(200, {"Content-Type": "text/plain"});    response.write("Hello World");    response.end();  }  http.createServer(onRequest).listen(8888);  console.log("Server has started.");}exports.start = start;

好了,我们的应用现在可以通过请求的URL路径来区别不同请求了--这使我们得以使用路由(还未完成)来将请求以URL路径为基准映射到处理程序上。

在我们所要构建的应用中,这意味着来自/start和/upload的请求可以使用不同的代码来处理。稍后我们将看到这些内容是如何整合到一起的。

现在我们可以来编写路由了,建立一个名为router.js的文件,添加以下内容:

function route(pathname) {  console.log("About to route a request for " + pathname);}exports.route = route;

如你所见,这段代码什么也没干,不过对于现在来说这是应该的。在添加更多的逻辑以前,我们先来看看如何把路由和服务器整合起来。

我们的服务器应当知道路由的存在并加以有效利用。我们当然可以通过硬编码的方式将这一依赖项绑定到服务器上,但是其它语言的编程经验告诉我们这会是一件非常痛苦的事,因此我们将使用依赖注入的方式较松散地添加路由模块。

首先,我们来扩展一下服务器的 start() 函数,以便将路由函数作为参数传递过去,server.js 文件代码如下

var http = require("http");var url = require("url"); function start(route) {  function onRequest(request, response) {    var pathname = url.parse(request.url).pathname;    console.log("Request for " + pathname + " received.");     route(pathname);     response.writeHead(200, {"Content-Type": "text/plain"});    response.write("Hello World");    response.end();  }   http.createServer(onRequest).listen(8888);  console.log("Server has started.");} exports.start = start;

同时,我们会相应扩展index.js,使得路由函数可以被注入到服务器中:

var server = require("./server");var router = require("./router");server.start(router.route);

在这里,我们传递的函数依旧什么也没做。

如果现在启动应用(node index.js,始终记得这个命令行),随后请求一个URL,你将会看到应用输出相应的信息,这表明我们的HTTP服务器已经在使用路由模块了,并会将请求的路径传递给路由:

bash$ node index.jsRequest for /foo received.About to route a request for /foo

以上输出已经去掉了比较烦人的/favicon.ico请求相关的部分。

浏览器访问 http://127.0.0.1:8888/,输出结果如下:


JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可以在程序的任何地方访问,即全局变量。

在浏览器 JavaScript 中,通常 window 是全局对象, 而 Node.js 中的全局对象是 global,所有全局变量(除了 global 本身以外)都是 global 对象的属性。

在 Node.js 我们可以直接访问到 global 的属性,而不需要在应用中包含它。


全局对象与全局变量

global 最根本的作用是作为全局变量的宿主。按照 ECMAScript 的定义,满足以下条件的变量是全局变量:

  • 在最外层定义的变量;
  • 全局对象的属性;
  • 隐式定义的变量(未定义直接赋值的变量)。

当你定义一个全局变量时,这个变量同时也会成为全局对象的属性,反之亦然。需要注 意的是,在Node.js 中你不可能在最外层定义变量,因为所有用户代码都是属于当前模块的, 而模块本身不是最外层上下文。

注意: 永远使用var 定义变量以避免引入全局变量,因为全局变量会污染 命名空间,提高代码的耦合风险。


__filename

__filename 表示当前正在执行的脚本的文件名。它将输出文件所在位置的绝对路径,且和命令行参数所指定的文件名不一定相同。 如果在模块中,返回的值是模块文件的路径。

实例

创建文件 main.js ,代码如下所示:

// 输出全局变量 __filename 的值console.log( __filename );

执行 main.js 文件,代码如下所示:

$ node main.js/web/com/51coolma/nodejs/main.js

__dirname

__dirname 表示当前执行脚本所在的目录。

实例

创建文件 main.js ,代码如下所示:

// 输出全局变量 __dirname 的值console.log( __dirname );

执行 main.js 文件,代码如下所示:

$ node main.js/web/com/51coolma/nodejs

setTimeout(cb, ms)

setTimeout(cb, ms) 全局函数在指定的毫秒(ms)数后执行指定函数(cb)。:setTimeout() 只执行一次指定函数。

返回一个代表定时器的句柄值。

实例

创建文件 main.js ,代码如下所示:

function printHello(){   console.log( "Hello, World!");}// 两秒后执行以上函数setTimeout(printHello, 2000);

执行 main.js 文件,代码如下所示:

$ node main.jsHello, World!

clearTimeout(t)

clearTimeout( t ) 全局函数用于停止一个之前通过 setTimeout() 创建的定时器。 参数 t 是通过 setTimeout() 函数创建的定时器。

实例

创建文件 main.js ,代码如下所示:

function printHello(){   console.log( "Hello, World!");}// 两秒后执行以上函数var t = setTimeout(printHello, 2000);// 清除定时器clearTimeout(t);

执行 main.js 文件,代码如下所示:

$ node main.js

setInterval(cb, ms)

setInterval(cb, ms) 全局函数在指定的毫秒(ms)数后执行指定函数(cb)。

返回一个代表定时器的句柄值。可以使用 clearInterval(t) 函数来清除定时器。

setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。

实例

创建文件 main.js ,代码如下所示:

function printHello(){   console.log( "Hello, World!");}// 两秒后执行以上函数setInterval(printHello, 2000);

执行 main.js 文件,代码如下所示:

$ node main.js

Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! ……

以上程序每隔两秒就会输出一次"Hello, World!",且会永久执行下去,直到你按下 ctrl + c 按钮。

console

console 用于提供控制台标准输出,它是由 Internet Explorer 的 JScript 引擎提供的调试工具,后来逐渐成为浏览器的实施标准。

Node.js 沿用了这个标准,提供与习惯行为一致的 console 对象,用于向标准输出流(stdout)或标准错误流(stderr)输出字符。

console 方法

以下为 console 对象的方法:

序号方法 & 描述
1console.log([data][, ...])
向标准输出流打印字符并以换行符结束。该方法接收若干 个参数,如果只有一个参数,则输出这个参数的字符串形式。如果有多个参数,则 以类似于C 语言 printf() 命令的格式输出。
2console.info([data][, ...])
该命令的作用是返回信息性消息,这个命令与console.log差别并不大,除了在chrome中只会输出文字外,其余的会显示一个蓝色的惊叹号。
3console.error([data][, ...])
输出错误消息的。控制台在出现错误时会显示是红色的叉子。
4console.warn([data][, ...])
输出警告消息。控制台出现有黄色的惊叹号。
5console.dir(obj[, options])
用来对一个对象进行检查(inspect),并以易于阅读和打印的格式显示。
6console.time(label)
输出时间,表示计时开始。
7console.timeEnd(label)
结束时间,表示计时结束。
8console.trace(message[, ...])
当前执行的代码在堆栈中的调用路径,这个测试函数运行很有帮助,只要给想测试的函数里面加入 console.trace 就行了。
9console.assert(value[, message][, ...])
用于判断某个表达式或变量是否为真,接收两个参数,第一个参数是表达式,第二个参数是字符串。只有当第一个参数为false,才会输出第二个参数,否则不会有任何结果。

console.log():向标准输出流打印字符并以换行符结束。

console.log 接收若干 个参数,如果只有一个参数,则输出这个参数的字符串形式。如果有多个参数,则 以类似于C 语言 printf() 命令的格式输出。

第一个参数是一个字符串,如果没有 参数,只打印一个换行。

console.log('Hello world'); console.log('byvoid%diovyb'); console.log('byvoid%diovyb', 1991); 

运行结果为:

Hello world byvoid%diovyb byvoid1991iovyb 
  • console.error():与console.log() 用法相同,只是向标准错误流输出。
  • console.trace():向标准错误流输出当前的调用栈。
console.trace();

运行结果为:

Trace: at Object.<anonymous> (/home/byvoid/consoletrace.js:1:71) 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)

实例

创建文件 main.js ,代码如下所示:

console.info("程序开始执行:");var counter = 10;console.log("计数: %d", counter);console.time("获取数据");//// 执行一些代码// console.timeEnd('获取数据');console.info("程序执行完毕。")

执行 main.js 文件,代码如下所示:

$ node main.js程序开始执行:计数: 10获取数据: 0ms程序执行完毕

process

process 是一个全局变量,即 global 对象的属性。

它用于描述当前Node.js 进程状态的对象,提供了一个与操作系统的简单接口。通常在你写本地命令行程序的时候,少不了要 和它打交道。下面将会介绍 process 对象的一些最常用的成员方法。

序号事件 & 描述
1exit
当进程准备退出时触发。
2beforeExit
当 node 清空事件循环,并且没有其他安排时触发这个事件。通常来说,当没有进程安排时 node 退出,但是 'beforeExit' 的监听器可以异步调用,这样 node 就会继续执行。
3uncaughtException
当一个异常冒泡回到事件循环,触发这个事件。如果给异常添加了监视器,默认的操作(打印堆栈跟踪信息并退出)就不会发生。
4Signal 事件
当进程接收到信号时就触发。信号列表详见标准的 POSIX 信号名,如 SIGINT、SIGUSR1 等。

实例

创建文件 main.js ,代码如下所示:

process.on('exit', function(code) {  // 以下代码永远不会执行  setTimeout(function() {    console.log("该代码不会执行");  }, 0);    console.log('退出码为:', code);});console.log("程序执行结束");

执行 main.js 文件,代码如下所示:

$ node main.js程序执行结束退出码为: 0

退出状态码

退出状态码如下所示:

状态码名称 & 描述
1Uncaught Fatal Exception
有未捕获异常,并且没有被域或 uncaughtException 处理函数处理。
2Unused
保留
3Internal JavaScript Parse Error
JavaScript的源码启动 Node 进程时引起解析错误。非常罕见,仅会在开发 Node 时才会有。
4Internal JavaScript Evaluation Failure
JavaScript 的源码启动 Node 进程,评估时返回函数失败。非常罕见,仅会在开发 Node 时才会有。
5Fatal Error
V8 里致命的不可恢复的错误。通常会打印到 stderr ,内容为: FATAL ERROR
6Non-function Internal Exception Handler
未捕获异常,内部异常处理函数不知为何设置为on-function,并且不能被调用。
7Internal Exception Handler Run-Time Failure
未捕获的异常, 并且异常处理函数处理时自己抛出了异常。例如,如果 process.on('uncaughtException') 或 domain.on('error') 抛出了异常。
8Unused
保留
9Invalid Argument
可能是给了未知的参数,或者给的参数没有值。
10Internal JavaScript Run-Time Failure
JavaScript的源码启动 Node 进程时抛出错误,非常罕见,仅会在开发 Node 时才会有。
12Invalid Debug Argument
设置了参数--debug 和/或 --debug-brk,但是选择了错误端口。
128Signal Exits
如果 Node 接收到致命信号,比如SIGKILL 或 SIGHUP,那么退出代码就是128 加信号代码。这是标准的 Unix 做法,退出信号代码放在高位。

Process 属性

Process 提供了很多有用的属性,便于我们更好的控制系统的交互:

序号.属性 & 描述
1stdout
标准输出流。
2stderr
标准错误流。
3stdin
标准输入流。
4argv
argv 属性返回一个数组,由命令行执行脚本时的各个参数组成。它的第一个成员总是node,第二个成员是脚本文件名,其余成员是脚本文件的参数。
5execPath
返回执行当前脚本的 Node 二进制文件的绝对路径。
6execArgv
返回一个数组,成员是命令行下执行脚本时,在Node可执行文件与脚本文件之间的命令行参数。
7env
返回一个对象,成员为当前 shell 的环境变量
8exitCode
进程退出时的代码,如果进程优通过 process.exit() 退出,不需要指定退出码。
9version
Node 的版本,比如v0.10.18。
10versions
一个属性,包含了 node 的版本和依赖.
11config
一个包含用来编译当前 node 执行文件的 javascript 配置选项的对象。它与运行 ./configure 脚本生成的 "config.gypi" 文件相同。
12pid
当前进程的进程号。
13title
进程名,默认值为"node",可以自定义该值。
14arch
当前 CPU 的架构:'arm'、'ia32' 或者 'x64'。
15platform
运行程序所在的平台系统 'darwin', 'freebsd', 'linux', 'sunos' 或 'win32'
16mainModule
require.main 的备选方法。不同点,如果主模块在运行时改变,require.main可能会继续返回老的模块。可以认为,这两者引用了同一个模块。

实例

创建文件 main.js ,代码如下所示:

// 输出到终端process.stdout.write("Hello World!" + "
");// 通过参数读取process.argv.forEach(function(val, index, array) {   console.log(index + ': ' + val);});// 获取执行路径console.log(process.execPath);// 平台信息console.log(process.platform);

执行 main.js 文件,代码如下所示:

$ node main.jsHello World!0: node1: /web/www/node/main.js/usr/local/node/0.10.36/bin/nodedarwin

方法参考手册

Process 提供了很多有用的方法,便于我们更好的控制系统的交互:

序号方法 & 描述
1abort()
这将导致 node 触发 abort 事件。会让 node 退出并生成一个核心文件。
2chdir(directory)
改变当前工作进程的目录,如果操作失败抛出异常。
3cwd()
返回当前进程的工作目录
4exit([code])
使用指定的 code 结束进程。如果忽略,将会使用 code 0。
5getgid()
获取进程的群组标识(参见 getgid(2))。获取到得时群组的数字 id,而不是名字。
注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。
6setgid(id)
设置进程的群组标识(参见 setgid(2))。可以接收数字 ID 或者群组名。如果指定了群组名,会阻塞等待解析为数字 ID 。
注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。
7getuid()
获取进程的用户标识(参见 getuid(2))。这是数字的用户 id,不是用户名。
注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。
8setuid(id)
设置进程的用户标识(参见setuid(2))。接收数字 ID或字符串名字。果指定了群组名,会阻塞等待解析为数字 ID 。
注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。
9getgroups()
返回进程的群组 iD 数组。POSIX 系统没有保证一定有,但是 node.js 保证有。
注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。
10setgroups(groups)
设置进程的群组 ID。这是授权操作,所以你需要有 root 权限,或者有 CAP_SETGID 能力。
注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。
11initgroups(user, extra_group)
读取 /etc/group ,并初始化群组访问列表,使用成员所在的所有群组。这是授权操作,所以你需要有 root 权限,或者有 CAP_SETGID 能力。
注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。
12kill(pid[, signal])
发送信号给进程. pid 是进程id,并且 signal 是发送的信号的字符串描述。信号名是字符串,比如 'SIGINT' 或 'SIGHUP'。如果忽略,信号会是 'SIGTERM'。
13memoryUsage()
返回一个对象,描述了 Node 进程所用的内存状况,单位为字节。
14nextTick(callback)
一旦当前事件循环结束,调用回调函数。
15umask([mask])
设置或读取进程文件的掩码。子进程从父进程继承掩码。如果mask 参数有效,返回旧的掩码。否则,返回当前掩码。
16uptime()
返回 Node 已经运行的秒数。
17hrtime()
返回当前进程的高分辨时间,形式为 [seconds, nanoseconds]数组。它是相对于过去的任意事件。该值与日期无关,因此不受时钟漂移的影响。主要用途是可以通过精确的时间间隔,来衡量程序的性能。
你可以将之前的结果传递给当前的 process.hrtime() ,会返回两者间的时间差,用来基准和测量时间间隔。

实例

创建文件 main.js ,代码如下所示:

// 输出当前目录console.log('当前目录: ' + process.cwd());// 输出当前版本console.log('当前版本: ' + process.version);// 输出内存使用情况console.log(process.memoryUsage());

执行 main.js 文件,代码如下所示:

$ node main.js当前目录: /web/com/51coolma/nodejs当前版本: v0.10.36{ rss: 12541952, heapTotal: 4083456, heapUsed: 2157056 }

相关教程

ECMAScript教程


util 是一个Node.js 核心模块,提供常用函数的集合,用于弥补核心 JavaScript 的功能 过于精简的不足。

使用方法如下:

const util = require('util');

util.callbackify

util.callbackify(original) 将 async 异步函数(或者一个返回值为 Promise 的函数)转换成遵循异常优先的回调风格的函数,例如将 (err, value) => ... 回调作为最后一个参数。 在回调函数中,第一个参数为拒绝的原因(如果 Promise 解决,则为 null),第二个参数则是解决的值。

实例

const util = require('util');async function fn() {  return 'hello world';}const callbackFunction = util.callbackify(fn);callbackFunction((err, ret) => {  if (err) throw err;  console.log(ret);});

以上代码输出结果为:

hello world

回调函数是异步执行的,并且有异常堆栈错误追踪。 如果回调函数抛出一个异常,进程会触发一个 'uncaughtException' 异常,如果没有被捕获,进程将会退出。

null 在回调函数中作为一个参数有其特殊的意义,如果回调函数的首个参数为 Promise 拒绝的原因且带有返回值,且值可以转换成布尔值 false,这个值会被封装在 Error 对象里,可以通过属性 reason 获取。

function fn() {  return Promise.reject(null);}const callbackFunction = util.callbackify(fn);callbackFunction((err, ret) => {  // 当 Promise 被以 `null` 拒绝时,它被包装为 Error 并且原始值存储在 `reason` 中。  err && err.hasOwnProperty('reason') && err.reason === null;  // true});

original 为 async 异步函数。该函数返回传统回调函数。

util.inherits

util.inherits(constructor, superConstructor) 是一个实现对象间原型继承的函数。

JavaScript 的面向对象特性是基于原型的,与常见的基于类的不同。JavaScript 没有提供对象继承的语言级别特性,而是通过原型复制来实现的。

在这里我们只介绍 util.inherits 的用法,示例如下:

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); 

我们定义了一个基础对象 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 #&lt;Sub&gt; has no method 'sayHello' at Object.&lt;anonymous&gt; (/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) 

util.inspect

util.inspect(object,[showHidden],[depth],[colors]) 是一个将任意对象转换 为字符串的方法,通常用于调试和错误输出。它至少接受一个参数 object,即要转换的对象。

showHidden 是一个可选参数,如果值为 true,将会输出更多隐藏信息。

depth 表示最大递归的层数,如果对象很复杂,你可以指定层数以控制输出信息的多 少。如果不指定depth,默认会递归 2 层,指定为 null 表示将不限递归层数完整遍历对象。 如果 colors 值为 true,输出格式将会以 ANSI 颜色编码,通常用于在终端显示更漂亮 的效果。

特别要指出的是,util.inspect 并不会简单地直接把对象转换为字符串,即使该对 象定义了 toString 方法也不会调用。

var util = require('util'); function Person() {     this.name = 'byvoid';     this.toString = function() {     return this.name;     }; } var obj = new Person(); console.log(util.inspect(obj)); console.log(util.inspect(obj, true)); 

运行结果是:

Person { name: 'byvoid', toString: [Function] }Person {  name: 'byvoid',  toString:    { [Function]     [length]: 0,     [name]: '',     [arguments]: null,     [caller]: null,     [prototype]: { [constructor]: [Circular] } } }

util.isArray(object)

如果给定的参数 "object" 是一个数组返回 true,否则返回 false。

var util = require('util');util.isArray([])  // trueutil.isArray(new Array)  // trueutil.isArray({})  // false

util.isRegExp(object)

如果给定的参数 "object" 是一个正则表达式返回true,否则返回false。

var util = require('util');util.isRegExp(/some regexp/)  // trueutil.isRegExp(new RegExp('another regexp'))  // trueutil.isRegExp({})  // false

util.isDate(object)

如果给定的参数 "object" 是一个日期返回true,否则返回false。

var util = require('util');util.isDate(new Date())  // trueutil.isDate(Date())  // false (without 'new' returns a String)util.isDate({})  // false

更多详情可以访问 http://nodejs.org/api/util.html 了解详细内容。


Node.js 提供一组类似 UNIX(POSIX)标准的文件操作API。 Node 导入文件系统模块(fs)语法如下所示:

var fs = require("fs")

异步和同步

Node.js 文件系统(fs 模块)模块中的方法均有异步和同步版本,例如读取文件内容的函数有异步的 fs.readFile() 和同步的 fs.readFileSync()。

异步的方法函数最后一个参数为回调函数,回调函数的第一个参数包含了错误信息(error)。

建议大家使用异步方法,比起同步,异步方法性能更高,速度更快,而且没有阻塞。

实例

创建 input.txt 文件,内容如下:

编程狮官网地址:www.51coolma.cn文件读取实例

创建 file.js 文件, 代码如下:

var fs = require("fs");// 异步读取fs.readFile('input.txt', function (err, data) {   if (err) {       return console.error(err);   }   console.log("异步读取: " + data.toString());});// 同步读取var data = fs.readFileSync('input.txt');console.log("同步读取: " + data.toString());console.log("程序执行完毕。");

以上代码执行结果如下:

$ node file.js 同步读取: 编程狮官网地址:www.51coolma.cn文件读取实例程序执行完毕。异步读取: 编程狮官网地址:www.51coolma.cn文件读取实例

接下来,让我们来具体了解下 Node.js 文件系统的方法。

打开文件

语法

以下为在异步模式下打开文件的语法格式:

fs.open(path, flags[, mode], callback)

参数

参数使用说明如下:

  • path - 文件的路径。
  • flags - 文件打开的行为。具体值详见下文。
  • mode - 设置文件模式(权限),文件创建默认权限为 0666(可读,可写)。
  • callback - 回调函数,带有两个参数如:callback(err, fd)。

flags 参数可以是以下值:

Flag描述
r以读取模式打开文件。如果文件不存在抛出异常。
r+以读写模式打开文件。如果文件不存在抛出异常。
rs以同步的方式读取文件。
rs+以同步的方式读取和写入文件。
w以写入模式打开文件,如果文件不存在则创建。
wx类似 'w',但是如果文件路径存在,则文件写入失败。
w+以读写模式打开文件,如果文件不存在则创建。
wx+类似 'w+', 但是如果文件路径存在,则文件读写失败。
a以追加模式打开文件,如果文件不存在则创建。
ax类似 'a', 但是如果文件路径存在,则文件追加失败。
a+以读取追加模式打开文件,如果文件不存在则创建。
ax+类似 'a+', 但是如果文件路径存在,则文件读取追加失败。

实例

接下来我们创建 file.js 文件,并打开 input.txt 文件进行读写,代码如下所示:

var fs = require("fs");// 异步打开文件console.log("准备打开文件!");fs.open('input.txt', 'r+', function(err, fd) {   if (err) {       return console.error(err);   }  console.log("文件打开成功!");     });

以上代码执行结果如下:

$ node file.js 准备打开文件!文件打开成功!

获取文件信息

语法

以下为通过异步模式获取文件信息的语法格式:

fs.stat(path, callback)

参数

参数使用说明如下:

  • path - 文件路径。
  • callback - 回调函数,带有两个参数如:(err, stats), stats 是 fs.Stats 对象。

fs.stat(path)执行后,会将stats类的实例返回给其回调函数。可以通过stats类中的提供方法判断文件的相关属性。例如判断是否为文件:

var fs = require('fs');fs.stat('/Users/liuht/code/itbilu/demo/fs.js', function (err, stats) {    console.log(stats.isFile());         //true})

stats类中的方法有:

方法描述
stats.isFile()如果是文件返回 true,否则返回 false。
stats.isDirectory()如果是目录返回 true,否则返回 false。
stats.isBlockDevice()如果是块设备返回 true,否则返回 false。
stats.isCharacterDevice()如果是字符设备返回 true,否则返回 false。
stats.isSymbolicLink()如果是软链接返回 true,否则返回 false。
stats.isFIFO()如果是FIFO,返回true,否则返回 false。FIFO是UNIX中的一种特殊类型的命令管道。
stats.isSocket()如果是 Socket 返回 true,否则返回 false。

实例

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");console.log("准备打开文件!");fs.stat('input.txt', function (err, stats) {   if (err) {       return console.error(err);   }   console.log(stats);   console.log("读取文件信息成功!");      // 检测文件类型   console.log("是否为文件(isFile) ? " + stats.isFile());   console.log("是否为目录(isDirectory) ? " + stats.isDirectory());    });

以上代码执行结果如下:

$ node file.js 准备打开文件!{ dev: 16777220,  mode: 33188,  nlink: 1,  uid: 501,  gid: 20,  rdev: 0,  blksize: 4096,  ino: 40333161,  size: 61,  blocks: 8,  atime: Mon Sep 07 2015 17:43:55 GMT+0800 (CST),  mtime: Mon Sep 07 2015 17:22:35 GMT+0800 (CST),  ctime: Mon Sep 07 2015 17:22:35 GMT+0800 (CST) }读取文件信息成功!是否为文件(isFile) ? true是否为目录(isDirectory) ? false

写入文件

语法

以下为异步模式下写入文件的语法格式:

fs.writeFile(file, data[, options], callback)

writeFile 直接打开文件默认是 w 模式,所以如果文件存在,该方法写入的内容会覆盖旧的文件内容。

参数

参数使用说明如下:

  • file - 文件名或文件描述符。
  • data - 要写入文件的数据,可以是 String(字符串) 或 Buffer(缓冲) 对象。
  • options - 该参数是一个对象,包含 {encoding, mode, flag}。默认编码为 utf8, 模式为 0666 , flag 为 'w'
  • callback - 回调函数,回调函数只包含错误信息参数(err),在写入失败时返回。

实例

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");console.log("准备写入文件");fs.writeFile('input.txt', '我是通 过fs.writeFile 写入文件的内容',  function(err) {   if (err) {       return console.error(err);   }   console.log("数据写入成功!");   console.log("--------我是分割线-------------")   console.log("读取写入的数据!");   fs.readFile('input.txt', function (err, data) {      if (err) {         return console.error(err);      }      console.log("异步读取文件数据: " + data.toString());   });});

以上代码执行结果如下:

$ node file.js 准备写入文件数据写入成功!--------我是分割线-------------读取写入的数据!异步读取文件数据: 我是通 过fs.writeFile 写入文件的内容

读取文件

语法

以下为异步模式下读取文件的语法格式:

fs.read(fd, buffer, offset, length, position, callback)

该方法使用了文件描述符来读取文件。

参数

参数使用说明如下:

  • fd - 通过 fs.open() 方法返回的文件描述符。
  • buffer - 数据写入的缓冲区。
  • offset - 缓冲区写入的写入偏移量。
  • length - 要从文件中读取的字节数。
  • position - 文件读取的起始位置,如果 position 的值为 null,则会从当前文件指针的位置读取。
  • callback - 回调函数,有三个参数err, bytesRead, buffer,err 为错误信息, bytesRead 表示读取的字节数,buffer 为缓冲区对象。

实例

input.txt 文件内容为:

编程狮官网地址:www.51coolma.cn

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");var buf = new Buffer.alloc(1024);console.log("准备打开已存在的文件!");fs.open('input.txt', 'r+', function(err, fd) {   if (err) {       return console.error(err);   }   console.log("文件打开成功!");   console.log("准备读取文件:");   fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){      if (err){         console.log(err);      }      console.log(bytes + "  字节被读取");            // 仅输出读取的字节      if(bytes > 0){         console.log(buf.slice(0, bytes).toString());      }   });});

以上代码执行结果如下:

$ node file.js 准备打开已存在的文件!文件打开成功!准备读取文件:42  字节被读取编程狮官网地址:www.51coolma.cn

关闭文件

语法

以下为异步模式下关闭文件的语法格式:

fs.close(fd, callback)

该方法使用了文件描述符来读取文件。

参数

参数使用说明如下:

  • fd - 通过 fs.open() 方法返回的文件描述符。
  • callback - 回调函数,没有参数。

实例

input.txt 文件内容为:

编程狮官网地址:www.51coolma.cn

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");var buf = new Buffer.alloc(1024);console.log("准备打开文件!");fs.open('input.txt', 'r+', function(err, fd) {   if (err) {       return console.error(err);   }   console.log("文件打开成功!");   console.log("准备读取文件!");   fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){      if (err){         console.log(err);      }      // 仅输出读取的字节      if(bytes > 0){         console.log(buf.slice(0, bytes).toString());      }      // 关闭文件      fs.close(fd, function(err){         if (err){            console.log(err);         }          console.log("文件关闭成功");      });   });});

以上代码执行结果如下:

$ node file.js 准备打开文件!文件打开成功!准备读取文件!

编程狮官网地址:www.51coolma.cn

文件关闭成功

截取文件

语法

以下为异步模式下截取文件的语法格式:

fs.ftruncate(fd, len, callback)

该方法使用了文件描述符来读取文件。

参数

参数使用说明如下:

  • fd - 通过 fs.open() 方法返回的文件描述符。
  • len - 文件内容截取的长度。
  • callback - 回调函数,没有参数。

实例

input.txt 文件内容为:

site:www.51coolma.com

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");var buf = new Buffer.alloc(1024);console.log("准备打开文件!");fs.open('input.txt', 'r+', function(err, fd) {   if (err) {       return console.error(err);   }   console.log("文件打开成功!");   console.log("截取10字节内的文件内容,超出部分将被去除。");      // 截取文件   fs.ftruncate(fd, 10, function(err){      if (err){         console.log(err);      }       console.log("文件截取成功。");      console.log("读取相同的文件");       fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){         if (err){            console.log(err);         }         // 仅输出读取的字节         if(bytes > 0){            console.log(buf.slice(0, bytes).toString());         }         // 关闭文件         fs.close(fd, function(err){            if (err){               console.log(err);            }             console.log("文件关闭成功!");         });      });   });});

以上代码执行结果如下:

$ node file.js 准备打开文件!文件打开成功!截取10字节内的文件内容,超出部分将被去除。文件截取成功。读取相同的文件site:www.r文件关闭成功

删除文件

语法

以下为删除文件的语法格式:

fs.unlink(path, callback)

参数

参数使用说明如下:

  • path - 文件路径。
  • callback - 回调函数,没有参数。

实例

input.txt 文件内容为:

site:www.51coolma.com

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");console.log("准备删除文件!");fs.unlink('input.txt', function(err) {   if (err) {       return console.error(err);   }   console.log("文件删除成功!");});

以上代码执行结果如下:

$ node file.js 准备删除文件!文件删除成功!

再去查看 input.txt 文件,发现已经不存在了。

创建目录

语法

以下为创建目录的语法格式:

fs.mkdir(path[, options], callback)

参数

参数使用说明如下:

  • path - 文件路径。
  • options 参数可以是:recursive - 是否以递归的方式创建目录,默认为 false。mode - 设置目录权限,默认为 0777。
  • callback - 回调函数,没有参数。

实例

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");// tmp 目录必须存在console.log("创建目录 /tmp/test/");fs.mkdir("/tmp/test/",function(err){   if (err) {       return console.error(err);   }   console.log("目录创建成功。");});

以上代码执行结果如下:

$ node file.js 创建目录 /tmp/test/目录创建成功。

可以添加 recursive: true 参数,不管创建的目录 /tmp 和 /tmp/a 是否存在:

fs.mkdir('/tmp/a/apple', { recursive: true }, (err) => {  if (err) throw err;});

读取目录

语法

以下为读取目录的语法格式:

fs.readdir(path, callback)

参数

参数使用说明如下:

  • path - 文件路径。
  • callback - 回调函数,回调函数带有两个参数err, files,err 为错误信息,files 为 目录下的文件数组列表。

实例

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");console.log("查看 /tmp 目录");fs.readdir("/tmp/",function(err, files){   if (err) {       return console.error(err);   }   files.forEach( function (file){       console.log( file );   });});

以上代码执行结果如下:

$ node file.js 查看 /tmp 目录input.outoutput.outtesttest.txt

删除目录

语法

以下为删除目录的语法格式:

fs.rmdir(path, callback)

参数

参数使用说明如下:

  • path - 文件路径。
  • callback - 回调函数,没有参数。

实例

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");// 执行前创建一个空的 /tmp/test 目录console.log("准备删除目录 /tmp/test");fs.rmdir("/tmp/test",function(err){   if (err) {       return console.error(err);   }   console.log("读取 /tmp 目录");   fs.readdir("/tmp/",function(err, files){      if (err) {          return console.error(err);      }      files.forEach( function (file){          console.log( file );      });   });});

以上代码执行结果如下:

$ node file.js 准备删除目录 /tmp/test读取 /tmp 目录……

文件模块方法参考手册

以下为 Node.js 文件模块相同的方法列表:

序号方法 & 描述
1fs.rename(oldPath, newPath, callback)
异步 rename().回调函数没有参数,但可能抛出异常。
2fs.ftruncate(fd, len, callback)
异步 ftruncate().回调函数没有参数,但可能抛出异常。
3fs.ftruncateSync(fd, len)
同步 ftruncate()
4fs.truncate(path, len, callback)
异步 truncate().回调函数没有参数,但可能抛出异常。
5fs.truncateSync(path, len)
同步 truncate()
6fs.chown(path, uid, gid, callback)
异步 chown().回调函数没有参数,但可能抛出异常。
7fs.chownSync(path, uid, gid)
同步 chown()
8fs.fchown(fd, uid, gid, callback)
异步 fchown().回调函数没有参数,但可能抛出异常。
9fs.fchownSync(fd, uid, gid)
同步 fchown()
10fs.lchown(path, uid, gid, callback)
异步 lchown().回调函数没有参数,但可能抛出异常。
11fs.lchownSync(path, uid, gid)
同步 lchown()
12fs.chmod(path, mode, callback)
异步 chmod().回调函数没有参数,但可能抛出异常。
13fs.chmodSync(path, mode)
同步 chmod().
14fs.fchmod(fd, mode, callback)
异步 fchmod().回调函数没有参数,但可能抛出异常。
15fs.fchmodSync(fd, mode)
同步 fchmod().
16fs.lchmod(path, mode, callback)
异步 lchmod().回调函数没有参数,但可能抛出异常。Only available on Mac OS X.
17fs.lchmodSync(path, mode)
同步 lchmod().
18fs.stat(path, callback)
异步 stat(). 回调函数有两个参数 err, stats,stats 是 fs.Stats 对象。
19fs.lstat(path, callback)
异步 lstat(). 回调函数有两个参数 err, stats,stats 是 fs.Stats 对象。
20fs.fstat(fd, callback)
异步 fstat(). 回调函数有两个参数 err, stats,stats 是 fs.Stats 对象。
21fs.statSync(path)
同步 stat(). 返回 fs.Stats 的实例。
22fs.lstatSync(path)
同步 lstat(). 返回 fs.Stats 的实例。
23fs.fstatSync(fd)
同步 fstat(). 返回 fs.Stats 的实例。
24fs.link(srcpath, dstpath, callback)
异步 link().回调函数没有参数,但可能抛出异常。
25fs.linkSync(srcpath, dstpath)
同步 link().
26fs.symlink(srcpath, dstpath[, type], callback)
异步 symlink().回调函数没有参数,但可能抛出异常。 type 参数可以设置为 'dir', 'file', 或 'junction' (默认为 'file') 。
27fs.symlinkSync(srcpath, dstpath[, type])
同步 symlink().
28fs.readlink(path, callback)
异步 readlink(). 回调函数有两个参数 err, linkString。
29fs.realpath(path[, cache], callback)
异步 realpath(). 回调函数有两个参数 err, resolvedPath。
30fs.realpathSync(path[, cache])
同步 realpath()。返回绝对路径。
31fs.unlink(path, callback)
异步 unlink().回调函数没有参数,但可能抛出异常。
32fs.unlinkSync(path)
同步 unlink().
33fs.rmdir(path, callback)
异步 rmdir().回调函数没有参数,但可能抛出异常。
34fs.rmdirSync(path)
同步 rmdir().
35fs.mkdir(path[, mode], callback)
S异步 mkdir(2).回调函数没有参数,但可能抛出异常。 访问权限默认为 0777。
36fs.mkdirSync(path[, mode])
同步 mkdir().
37fs.readdir(path, callback)
异步 readdir(3). 读取目录的内容。
38fs.readdirSync(path)
同步 readdir().返回文件数组列表。
39fs.close(fd, callback)
异步 close().回调函数没有参数,但可能抛出异常。
40fs.closeSync(fd)
同步 close().
41fs.open(path, flags[, mode], callback)
异步打开文件。
42fs.openSync(path, flags[, mode])
同步 version of fs.open().
43fs.utimes(path, atime, mtime, callback)
 
44fs.utimesSync(path, atime, mtime)
修改文件时间戳,文件通过指定的文件路径。
45fs.futimes(fd, atime, mtime, callback)
 
46fs.futimesSync(fd, atime, mtime)
修改文件时间戳,通过文件描述符指定。
47fs.fsync(fd, callback)
异步 fsync.回调函数没有参数,但可能抛出异常。
48fs.fsyncSync(fd)
同步 fsync.
49fs.write(fd, buffer, offset, length[, position], callback)
将缓冲区内容写入到通过文件描述符指定的文件。
50fs.write(fd, data[, position[, encoding]], callback)
通过文件描述符 fd 写入文件内容。
51fs.writeSync(fd, buffer, offset, length[, position])
同步版的 fs.write()。
52fs.writeSync(fd, data[, position[, encoding]])
同步版的 fs.write().
53fs.read(fd, buffer, offset, length, position, callback)
通过文件描述符 fd 读取文件内容。
54fs.readSync(fd, buffer, offset, length, position)
同步版的 fs.read.
55fs.readFile(filename[, options], callback)
异步读取文件内容。
56fs.readFileSync(filename[, options])
57fs.writeFile(filename, data[, options], callback)
异步写入文件内容。
58fs.writeFileSync(filename, data[, options])
同步版的 fs.writeFile。
59fs.appendFile(filename, data[, options], callback)
异步追加文件内容。
60fs.appendFileSync(filename, data[, options])
The 同步 version of fs.appendFile.
61fs.watchFile(filename[, options], listener)
查看文件的修改。
62fs.unwatchFile(filename[, listener])
停止查看 filename 的修改。
63fs.watch(filename[, options][, listener])
查看 filename 的修改,filename 可以是文件或目录。返回 fs.FSWatcher 对象。
64fs.exists(path, callback)
检测给定的路径是否存在。
65fs.existsSync(path)
同步版的 fs.exists.
66fs.access(path[, mode], callback)
测试指定路径用户权限。
67fs.accessSync(path[, mode])
同步版的 fs.access。
68fs.createReadStream(path[, options])
返回ReadStream 对象。
69fs.createWriteStream(path[, options])
返回 WriteStream 对象。
70fs.symlink(srcpath, dstpath[, type], callback)
异步 symlink().回调函数没有参数,但可能抛出异常。

更多详情可点击查看:http://nodejs.org/api/fs.html


在很多场景中,我们的服务器都需要跟用户的浏览器打交道,如表单提交。

表单提交到服务器一般都使用 GET/POST 请求。

本章节我们将为大家介绍 Node.js GET/POST请求。

获取GET请求内容

由于GET请求直接被嵌入在路径中,URL是完整的请求路径,包括了?后面的部分,因此你可以手动解析后面的内容作为GET请求的参数。

node.js 中 url 模块中的 parse 函数提供了这个功能。

实例

var http = require('http');var url = require('url');var util = require('util'); http.createServer(function(req, res){    res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});    res.end(util.inspect(url.parse(req.url, true)));}).listen(3000);

在浏览器中访问 http://localhost:3000/user?name=编程狮&url=www.51coolma.com 然后查看返回结果:


获取 URL 的参数

我们可以使用 url.parse 方法来解析 URL 中的参数,代码如下:

实例

var http = require('http');var url = require('url');var util = require('util'); http.createServer(function(req, res){    res.writeHead(200, {'Content-Type': 'text/plain'});     // 解析 url 参数    var params = url.parse(req.url, true).query;    res.write("网站名:" + params.name);    res.write("
");    res.write("网站 URL:" + params.url);    res.end(); }).listen(3000);

在浏览器中访问 http://localhost:3000/user?name=编程狮&url=www.runoob.com 然后查看返回结果:


获取 POST 请求内容

POST 请求的内容全部的都在请求体中,http.ServerRequest 并没有一个属性内容为请求体,原因是等待请求体传输可能是一件耗时的工作。

比如上传文件,而很多时候我们可能并不需要理会请求体的内容,恶意的POST请求会大大消耗服务器的资源,所以 node.js 默认是不会解析请求体的,当你需要的时候,需要手动来做。

基本语法结构说明

var http = require('http');var querystring = require('querystring');var util = require('util'); http.createServer(function(req, res){    // 定义了一个post变量,用于暂存请求体的信息    var post = '';          // 通过req的data事件监听函数,每当接受到请求体的数据,就累加到post变量中    req.on('data', function(chunk){            post += chunk;    });     // 在end事件触发后,通过querystring.parse将post解析为真正的POST请求格式,然后向客户端返回。    req.on('end', function(){            post = querystring.parse(post);        res.end(util.inspect(post));    });}).listen(3000);

以下实例表单通过 POST 提交并输出数据:

实例

var http = require('http');var querystring = require('querystring'); var postHTML =   '<html><head><meta charset="utf-8"><title>编程狮 Node.js 实例</title></head>' +  '<body>' +  '<form method="post">' +  '网站名: <input name="name"><br>' +  '网站 URL: <input name="url"><br>' +  '<input type="submit">' +  '</form>' +  '</body></html>'; http.createServer(function (req, res) {  var body = "";  req.on('data', function (chunk) {    body += chunk;  });  req.on('end', function () {    // 解析参数    body = querystring.parse(body);    // 设置响应头部信息及编码    res.writeHead(200, {'Content-Type': 'text/html; charset=utf8'});     if(body.name && body.url) { // 输出提交的数据        res.write("网站名:" + body.name);        res.write("<br>");        res.write("网站 URL:" + body.url);    } else {  // 输出表单        res.write(postHTML);    }    res.end();  });}).listen(3000);



在 Node.js 模块库中有很多好用的模块。这些模块都是很常见的,并同时开发基于任何节点的应用程序频繁使用。接下来我们为大家介绍几种常用模块的使用:

序号模块名 & 描述
1OS 模块
提供基本的系统操作函数。
2Path 模块
提供了处理和转换文件路的工具。
3Net 模块
用于底层的网络通信。提供了服务端和客户端的的操作。
4DNS 模块
用于解析域名。
5Domain 模块
简化异步代码的异常处理,可以捕捉处理try catch无法捕捉的。

以上就是常用的Node.js工具模块,点击表格中的链接能够得到更多内容。


本节介绍Node.js Web模块,首先,你应该先了解什么是Web服务器。


什么是 Web 服务器?

Web服务器一般指网站服务器,是指驻留于因特网上某种类型计算机的程序。

Web服务器的基本功能就是提供Web信息浏览服务。它只需支持HTTP协议、HTML文档格式及URL,与客户端的网络浏览器配合。

大多数web服务器都支持服务端的脚本语言(php、python、ruby)等,并通过脚本语言从数据库获取数据,将结果返回给客户端浏览器。

目前最主流的三个Web服务器是Apache、Nginx、IIS。


Web 应用架构

  • Client - 客户端,一般指浏览器,浏览器可以通过HTTP协议向服务器请求数据。

  • Server - 服务端,一般指Web服务器,可以接收客户端请求,并向客户端发送响应数据。

  • Business - 业务层, 通过Web服务器处理应用程序,如与数据库交互,逻辑运算,调用外部程序等。

  • Data - 数据层,一般由数据库组成。


使用 Node 创建 Web 服务器

Node.js提供了http模块,http模块主要用于搭建HTTP服务端和客户端,如果要使用HTTP服务器或客户端功能,则必须调用http模块,代码如下:

var http = require('http');

以下是演示一个最基本的HTTP服务器架构(使用8081端口),创建server.js文件,代码如下所示:

var http = require('http');var fs = require('fs');var url = require('url');// 创建服务器http.createServer( function (request, response) {     // 解析请求,包括文件名   var pathname = url.parse(request.url).pathname;      // 输出请求的文件名   console.log("Request for " + pathname + " received.");      // 从文件系统中读取请求的文件内容   fs.readFile(pathname.substr(1), function (err, data) {      if (err) {         console.log(err);         // HTTP 状态码: 404 : NOT FOUND         // Content Type: text/plain         response.writeHead(404, {'Content-Type': 'text/html'});      }else{	                  // HTTP 状态码: 200 : OK         // Content Type: text/plain         response.writeHead(200, {'Content-Type': 'text/html'});	                  // 响应文件内容         response.write(data.toString());		      }      //  发送响应数据      response.end();   });   }).listen(8081);// 控制台会输出以下信息console.log('Server running at http://127.0.0.1:8081/');

接下来我们在该目录下创建一个index.html文件,代码如下:

<html><head><title>Sample Page</title></head><body>Hello World!</body></html>

执行server.js文件:

$ node server.jsServer running at http://127.0.0.1:8081/

接着我们在浏览器中输入并打开地址:http://127.0.0.1:8081/index.html,显示如下图所示:

执行server.js的控制台输出信息如下:

Server running at http://127.0.0.1:8081/Request for /index.html received.     #  客户端请求信息

Gif 实例演示

3

使用 Node 创建 Web 客户端

使用Node创建Web客户端需要引入http模块,创建client.js文件,代码如下所示:

var http = require('http');// 用于请求的选项var options = {   host: 'localhost',   port: '8081',   path: '/index.html'  };// 处理响应的回调函数var callback = function(response){   // 不断更新数据   var body = '';   response.on('data', function(data) {      body += data;   });      response.on('end', function() {      // 数据接收完成      console.log(body);   });}// 向服务端发送请求var req = http.request(options, callback);req.end();

新开一个终端,执行client.js文件,输出结果如下:

$ node client.js<html><head><title>Sample Page</title></head><body>Hello World!</body></html>

执行server.js的控制台输出信息如下:

Server running at http://127.0.0.1:8081/Request for /index.html received.   # 客户端请求信息


Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。


Express 简介

Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。

使用Express可以快速地搭建一个完整功能的网站。

Express 框架核心特性包括:

  • 可以设置中间件来响应HTTP请求。

  • 定义了路由表用于执行不同的HTTP请求动作。

  • 可以通过向模板传递参数来动态渲染HTML页面。


安装 Express

安装Express并将其保存到依赖列表中:

$ npm install express --save

以上命令会将Express框架安装在当期目录的node_modules目录中, node_modules目录下会自动创建express目录。以下几个重要的模块是需要与express框架一起安装的:

  • body-parser - node.js中间件,用于处理JSON, Raw, Text和URL编码的数据。

  • cookie-parser - 这就是一个解析Cookie的工具。通过req.cookies可以取到传过来的cookie,并把它们转成对象。

  • multer - node.js中间件,用于处理enctype="multipart/form-data"(设置表单的MIME编码)的表单数据。

$ npm install body-parser --save$ npm install cookie-parser --save$ npm install multer --save

第一个 Express 框架实例

接下来我们使用Express框架来输出"Hello World"。

以下实例中我们引入了express模块,并在客户端发起请求后,响应"Hello World"字符串。

创建express_demo.js文件,代码如下所示:

//express_demo.js 文件var express = require('express');var app = express();app.get('/', function (req, res) {   res.send('Hello World');})var server = app.listen(8081, function () {  var host = server.address().address  var port = server.address().port  console.log("应用实例,访问地址为 http://%s:%s", host, port)})

执行以上代码:

$ node express_demo.js 应用实例,访问地址为 http://0.0.0.0:8081

在浏览器中访问http://127.0.0.1:8081,结果如下图所示:


请求和响应

Express应用使用回调函数的参数: requestresponse对象来处理请求和响应的数据。

app.get('/', function (req, res) {   // --})

requestresponse对象的具体介绍:

Request 对象 - request对象表示HTTP请求,包含了请求查询字符串,参数,内容,HTTP头部等属性。常见属性有:

  1. req.app:当callback为外部文件时,用req.app访问express的实例
  2. req.baseUrl:获取路由当前安装的URL路径
  3. req.body / req.cookies:获得「请求主体」/ Cookies
  4. req.fresh / req.stale:判断请求是否还「新鲜」
  5. req.hostname / req.ip:获取主机名和IP地址
  6. req.originalUrl:获取原始请求URL
  7. req.params:获取路由的parameters
  8. req.path:获取请求路径
  9. req.protocol:获取协议类型
  10. req.query:获取URL的查询参数串
  11. req.route:获取当前匹配的路由
  12. req.subdomains:获取子域名
  13. req.accpets():检查请求的Accept头的请求类型
  14. req.acceptsCharsets / req.acceptsEncodings / req.acceptsLanguages
  15. req.get():获取指定的HTTP请求头
  16. req.is():判断请求头Content-Type的MIME类型

Response 对象 - response对象表示HTTP响应,即在接收到请求时向客户端发送的HTTP响应数据。常见属性有:

  1. res.app:同req.app一样
  2. res.append():追加指定HTTP头
  3. res.set()在res.append()后将重置之前设置的头
  4. res.cookie(name,value [,option]):设置Cookie
  5. opition: domain / expires / httpOnly / maxAge / path / secure / signed
  6. res.clearCookie():清除Cookie
  7. res.download():传送指定路径的文件
  8. res.get():返回指定的HTTP头
  9. res.json():传送JSON响应
  10. res.jsonp():传送JSONP响应
  11. res.location():只设置响应的Location HTTP头,不设置状态码或者close response
  12. res.redirect():设置响应的Location HTTP头,并且设置状态码302
  13. res.send():传送HTTP响应
  14. res.sendFile(path [,options] [,fn]):传送指定路径的文件 -会自动根据文件extension设定Content-Type
  15. res.set():设置HTTP头,传入object可以一次设置多个头
  16. res.status():设置HTTP状态码
  17. res.type():设置Content-Type的MIME类型

路由

我们已经了解了HTTP请求的基本应用,而路由决定了由谁(指定脚本)去响应客户端请求。

在HTTP请求中,我们可以通过路由提取出请求的URL以及GET/POST参数。

接下来我们扩展Hello World,添加一些功能来处理更多类型的HTTP请求。

创建express_demo2.js文件,代码如下所示:

var express = require('express');var app = express();//  主页输出 "Hello World"app.get('/', function (req, res) {   console.log("主页 GET 请求");   res.send('Hello GET');})//  POST 请求app.post('/', function (req, res) {   console.log("主页 POST 请求");   res.send('Hello POST');})//  /del_user 页面响应app.delete('/del_user', function (req, res) {   console.log("/del_user 响应 DELETE 请求");   res.send('删除页面');})//  /list_user 页面 GET 请求app.get('/list_user', function (req, res) {   console.log("/list_user GET 请求");   res.send('用户列表页面');})// 对页面 abcd, abxcd, ab123cd, 等响应 GET 请求app.get('/ab*cd', function(req, res) {      console.log("/ab*cd GET 请求");   res.send('正则匹配');})var server = app.listen(8081, function () {  var host = server.address().address  var port = server.address().port  console.log("应用实例,访问地址为 http://%s:%s", host, port)})

执行以上代码:

$ node express_demo2.js 应用实例,访问地址为 http://0.0.0.0:8081

接下来你可以尝试访问http://127.0.0.1:8081不同的地址,查看效果。

在浏览器中访问http://127.0.0.1:8081/list_user,结果如下图所示:

1

在浏览器中访问http://127.0.0.1:8081/abcd,结果如下图所示:

1

在浏览器中访问http://127.0.0.1:8081/abcdefg,结果如下图所示:

11

静态文件

Express提供了内置的中间件express.static来设置静态文件如:图片,CSS, JavaScript等。

你可以使用express.static中间件来设置静态文件路径。例如,如果你将图片, CSS, JavaScript文件放在public目录下,你可以这么写:

app.use(express.static('public'));

我们可以到public/images目录下放些图片,如下所示:

node_modulesserver.jspublic/public/imagespublic/images/logo.png

让我们再修改下"Hello Word"应用添加处理静态文件的功能。

创建express_demo3.js文件,代码如下所示:

var express = require('express');var app = express();app.use(express.static('public'));app.get('/', function (req, res) {   res.send('Hello World');})var server = app.listen(8081, function () {  var host = server.address().address  var port = server.address().port  console.log("应用实例,访问地址为 http://%s:%s", host, port)})

执行以上代码:

$ node express_demo3.js 应用实例,访问地址为 http://0.0.0.0:8081

执行以上代码:

在浏览器中访问 http://127.0.0.1:8081/images/logo.png(本实例采用了W3Cschool教程的logo),结果如下图所示:


GET 方法

以下实例演示了在表单中通过GET方法提交两个参数,我们可以使用server.js文件内的process_get路由器来处理输入:

index.htm文件代码如下:

<html><body><form action="http://127.0.0.1:8081/process_get" method="GET">First Name: <input type="text" name="first_name">  <br>Last Name: <input type="text" name="last_name"><input type="submit" value="Submit"></form></body></html>

server.js文件代码如下:

var express = require('express');var app = express();app.use(express.static('public'));app.get('/index.htm', function (req, res) {   res.sendFile( __dirname + "/" + "index.htm" );})app.get('/process_get', function (req, res) {   // 输出 JSON 格式   response = {       first_name:req.query.first_name,       last_name:req.query.last_name   };   console.log(response);   res.end(JSON.stringify(response));})var server = app.listen(8081, function () {  var host = server.address().address  var port = server.address().port  console.log("应用实例,访问地址为 http://%s:%s", host, port)})

执行以上代码:

node server.js 应用实例,访问地址为 http://0.0.0.0:8081

浏览器访问 http://127.0.0.1:8081/index.htm,如图所示:

1

现在你可以向表单输入数据,并提交,如下演示:

4

POST 方法

以下实例演示了在表单中通过POST方法提交两个参数,我们可以使用server.js文件内的process_post路由器来处理输入:

index.htm文件代码修改如下:

<html><body><form action="http://127.0.0.1:8081/process_post" method="POST">First Name: <input type="text" name="first_name">  <br>Last Name: <input type="text" name="last_name"><input type="submit" value="Submit"></form></body></html>

server.js文件代码修改如下:

var express = require('express');var app = express();var bodyParser = require('body-parser');// 创建 application/x-www-form-urlencoded 编码解析var urlencodedParser = bodyParser.urlencoded({ extended: false })app.use(express.static('public'));app.get('/index.htm', function (req, res) {   res.sendFile( __dirname + "/" + "index.htm" );})app.post('/process_post', urlencodedParser, function (req, res) {   // 输出 JSON 格式   response = {       first_name:req.body.first_name,       last_name:req.body.last_name   };   console.log(response);   res.end(JSON.stringify(response));})var server = app.listen(8081, function () {  var host = server.address().address  var port = server.address().port  console.log("应用实例,访问地址为 http://%s:%s", host, port)})

执行以上代码:

$ node server.js应用实例,访问地址为 http://0.0.0.0:8081

浏览器访问http://127.0.0.1:8081/index.htm,如图所示:

1

现在你可以向表单输入数据,并提交,如下演示:

6

文件上传

以下我们创建一个用于上传文件的表单,使用POST方法,表单enctype属性设置为multipart/form-data。

index.htm文件代码修改如下:

<html><head><title>文件上传表单</title></head><body><h3>文件上传:</h3>选择一个文件上传: <br /><form action="/file_upload" method="post" enctype="multipart/form-data"><input type="file" name="image" size="50" /><br /><input type="submit" value="上传文件" /></form></body></html>

server.js文件代码修改如下:

var express = require('express');var app = express();var fs = require("fs");var bodyParser = require('body-parser');var multer  = require('multer');app.use(express.static('public'));app.use(bodyParser.urlencoded({ extended: false }));app.use(multer({ dest: '/tmp/'}).array('image'));app.get('/index.htm', function (req, res) {   res.sendFile( __dirname + "/" + "index.htm" );})app.post('/file_upload', function (req, res) {   console.log(req.files[0]);  // 上传的文件信息   var des_file = __dirname + "/" + req.files[0].originalname;   fs.readFile( req.files[0].path, function (err, data) {        fs.writeFile(des_file, data, function (err) {         if( err ){              console.log( err );         }else{               response = {                   message:'File uploaded successfully',                    filename:req.files[0].originalname              };          }          console.log( response );          res.end( JSON.stringify( response ) );       });   });})var server = app.listen(8081, function () {  var host = server.address().address  var port = server.address().port  console.log("应用实例,访问地址为 http://%s:%s", host, port)})

执行以上代码:

$ node server.js 应用实例,访问地址为 http://0.0.0.0:8081

浏览器访问http://127.0.0.1:8081/index.htm,如图所示:

1111

现在你可以向表单输入数据,并提交,如下演示:

1

Cookie 管理

我们可以使用中间件向Node.js服务器发送cookie信息,以下代码输出了客户端发送的cookie信息:

// express_cookie.js 文件var express      = require('express')var cookieParser = require('cookie-parser')var app = express()app.use(cookieParser())app.get('/', function(req, res) {  console.log("Cookies: ", req.cookies)})app.listen(8081)

执行以上代码:

$ node express_cookie.js 

现在你可以访问 http://127.0.0.1:8081 并查看终端信息的输出,如下演示:

3


本节介绍Node.js的RESTful API。

什么是 REST?

REST中文解释为,表述性状态传递(英文:Representational State Transfer,简称REST),是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。

表述性状态转移是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是RESTful。

需要注意的是,REST是设计风格而不是标准。REST通常基于使用HTTP,URI和XML(标准通用标记语言下的一个子集)以及HTML(标准通用标记语言下的一个应用)这些现有的广泛流行的协议和标准。REST通常使用JSON数据格式。

HTTP 方法

以下为REST基本架构的四个方法:
  • GET - 用于获取数据。

  • PUT - 用于添加数据。

  • DELETE - 用于删除数据。

  • POST - 用于更新或添加数据。


RESTful Web Services

Web service是一个平台独立的,低耦合的,自包含的、基于可编程的web的应用程序,可使用开放的XML(标准通用标记语言下的一个子集)标准来描述、发布、发现、协调和配置这些应用程序,用于开发分布式的互操作的应用程序。

RESTful是基于REST架构的Web Services。

由于轻量级以及通过HTTP直接传输数据的特性,Web服务的RESTful方法已经成为最常见的替代方法。可以使用各种语言(比如,Java程序、Perl、Ruby、Python、PHP和Javascript[包括Ajax])实现客户端。

RESTful Web服务通常可以通过自动客户端或代表用户的应用程序访问。但是,这种服务的简便性让用户能够与之直接交互,使用它们的Web浏览器构建一个GET URL并读取返回的内容。

更多介绍,可以查看:RESTful 架构详解


创建 RESTful

首先,创建一个json数据资源文件users.json,内容如下:

{   "user1" : {      "name" : "mahesh",	  "password" : "password1",	  "profession" : "teacher",	  "id": 1   },   "user2" : {      "name" : "suresh",	  "password" : "password2",	  "profession" : "librarian",	  "id": 2   },   "user3" : {      "name" : "ramesh",	  "password" : "password3",	  "profession" : "clerk",	  "id": 3   }}

基于以上数据,我们创建以下RESTful API:

序号URIHTTP 方法发送内容结果
1listUsersGET显示所有用户列表
2addUserPOSTJSON 字符串添加新用户
3deleteUserDELETEJSON 字符串删除用户
4:idGET显示用户详细信息

获取用户列表:

以下代码,我们创建了RESTful API listUsers,用于读取用户的信息列表, server.js文件代码如下所示:

var express = require('express');var app = express();var fs = require("fs");app.get('/listUsers', function (req, res) {   fs.readFile( __dirname + "/" + "users.json", 'utf8', function (err, data) {       console.log( data );       res.end( data );   });})var server = app.listen(8081, function () {  var host = server.address().address  var port = server.address().port  console.log("应用实例,访问地址为 http://%s:%s", host, port)})

接下来执行以下命令:

$ node server.js 应用实例,访问地址为 http://0.0.0.0:8081

在浏览器中访问http://127.0.0.1:8081/listUsers,结果如下所示:

{   "user1" : {      "name" : "mahesh",      "password" : "password1",      "profession" : "teacher",      "id": 1   },   "user2" : {      "name" : "suresh",      "password" : "password2",      "profession" : "librarian",      "id": 2   },   "user3" : {      "name" : "ramesh",      "password" : "password3",      "profession" : "clerk",      "id": 3   }}

添加用户

如果要添加新的用户数据,可以通过创建RESTful API addUser实现,server.js文件代码如下所示:

var express = require('express');var app = express();var fs = require("fs");//添加的新用户数据var user = {   "user4" : {      "name" : "mohit",      "password" : "password4",      "profession" : "teacher",      "id": 4   }}app.get('/addUser', function (req, res) {   // 读取已存在的数据   fs.readFile( __dirname + "/" + "users.json", 'utf8', function (err, data) {       data = JSON.parse( data );       data["user4"] = user["user4"];       console.log( data );       res.end( JSON.stringify(data));   });})var server = app.listen(8081, function () {  var host = server.address().address  var port = server.address().port  console.log("应用实例,访问地址为 http://%s:%s", host, port)})

接下来执行以下命令:

$ node server.js 应用实例,访问地址为 http://0.0.0.0:8081

在浏览器中访问http://127.0.0.1:8081/addUser,结果如下所示:

{ user1:   { name: 'mahesh',     password: 'password1',     profession: 'teacher',     id: 1 },  user2:   { name: 'suresh',     password: 'password2',     profession: 'librarian',     id: 2 },  user3:   { name: 'ramesh',     password: 'password3',     profession: 'clerk',     id: 3 },  user4:   { name: 'mohit',     password: 'password4',     profession: 'teacher',     id: 4 } }

显示用户详情

以下代码,我们创建了RESTful API :id(用户id), 用于读取指定用户的详细信息,server.js文件代码如下所示:

var express = require('express');var app = express();var fs = require("fs");app.get('/:id', function (req, res) {   // 首先我们读取已存在的用户   fs.readFile( __dirname + "/" + "users.json", 'utf8', function (err, data) {       data = JSON.parse( data );       var user = data["user" + req.params.id]        console.log( user );       res.end( JSON.stringify(user));   });})var server = app.listen(8081, function () {  var host = server.address().address  var port = server.address().port  console.log("应用实例,访问地址为 http://%s:%s", host, port)})

接下来执行以下命令:

$ node server.js 应用实例,访问地址为 http://0.0.0.0:8081

在浏览器中访问http://127.0.0.1:8081/2,结果如下所示:

{   "name":"suresh",   "password":"password2",   "profession":"librarian",   "id":2}

删除用户

以下代码,我们创建了RESTful API deleteUser, 用于删除指定用户的详细信息,以下实例中,用户id为2,server.js文件代码如下所示:

var express = require('express');var app = express();var fs = require("fs");var id = 2;app.get('/deleteUser', function (req, res) {   // First read existing users.   fs.readFile( __dirname + "/" + "users.json", 'utf8', function (err, data) {       data = JSON.parse( data );       delete data["user" + 2];              console.log( data );       res.end( JSON.stringify(data));   });})var server = app.listen(8081, function () {  var host = server.address().address  var port = server.address().port  console.log("应用实例,访问地址为 http://%s:%s", host, port)})

接下来执行以下命令:

$ node server.js 应用实例,访问地址为 http://0.0.0.0:8081

在浏览器中访问http://127.0.0.1:8081/deleteUser,结果如下所示:

{ user1:   { name: 'mahesh',     password: 'password1',     profession: 'teacher',     id: 1 },  user3:   { name: 'ramesh',     password: 'password3',     profession: 'clerk',     id: 3 } }


Node.js本身是以单线程的模式运行的,但它使用的是事件驱动来处理并发,这样有助于我们在多核 cpu 的系统上创建多个子进程,从而提高性能。

每个子进程总是带有三个流对象:child.stdin, child.stdout和child.stderr。他们可能会共享父进程的stdio流,或者也可以是独立的被导流的流对象。

Node提供了child_process模块来创建子进程,方法有:

  • exec - child_process.exec使用子进程执行命令,缓存子进程的输出,并将子进程的输出以回调函数参数的形式返回。

  • spawn - child_process.spawn使用指定的命令行参数创建新进程。

  • fork - child_process.fork是spawn()的特殊形式,用于在子进程中运行的模块,如fork('./son.js')相当于spawn('node', ['./son.js']) 。与spawn方法不同的是,fork会在父进程与子进程之间,建立一个通信管道,用于进程之间的通信。


exec() 方法

child_process.exec使用子进程执行命令,缓存子进程的输出,并将子进程的输出以回调函数参数的形式返回。

语法如下所示:

child_process.exec(command[, options], callback)

参数

参数说明如下:

command: 字符串, 将要运行的命令,参数使用空格隔开

options :对象,可以是:

  • cwd,字符串,子进程的当前工作目录
  • env,对象,环境变量键值对
  • encoding,字符串,字符编码(默认: 'utf8')
  • shell,字符串,将要执行命令的Shell(默认: 在UNIX中为/bin/sh, 在Windows中为cmd.exe, Shell应当能识别-c开关在UNIX中,或/s /c在 Windows中。 在Windows中,命令行解析应当能兼容cmd.exe
  • timeout,数字,超时时间(默认: 0)
  • maxBuffer,数字, 在stdout或stderr中允许存在的最大缓冲(二进制),如果超出那么子进程将会被杀死(默认: 200*1024)
  • killSignal,字符串,结束信号(默认:'SIGTERM')
  • uid,数字,设置用户进程的ID
  • gid,数字,设置进程组的ID

callback :回调函数,包含三个参数error, stdout和stderr。

exec()方法返回最大的缓冲区,并等待进程结束,一次性返回缓冲区的内容。

实例

让我们创建两个js文件support.js和master.js。

support.js文件代码:

console.log("进程 " + process.argv[2] + " 执行。" );

master.js文件代码:

const fs = require('fs');const child_process = require('child_process');for(var i=0; i<3; i++) {   var workerProcess = child_process.exec('node support.js '+i,      function (error, stdout, stderr) {         if (error) {            console.log(error.stack);            console.log('Error code: '+error.code);            console.log('Signal received: '+error.signal);         }         console.log('stdout: ' + stdout);         console.log('stderr: ' + stderr);      });      workerProcess.on('exit', function (code) {      console.log('子进程已退出,退出码 '+code);   });}

执行以上代码,输出结果为:

$ node master.js 子进程已退出,退出码 0stdout: 进程 1 执行。stderr: 子进程已退出,退出码 0stdout: 进程 0 执行。stderr: 子进程已退出,退出码 0stdout: 进程 2 执行。stderr: 

spawn() 方法

child_process.spawn使用指定的命令行参数创建新进程,语法格式如下:

child_process.spawn(command[, args][, options])

参数

参数说明如下:

command: 将要运行的命令

args: Array字符串参数数组

options Object

  • cwd:String,子进程的当前工作目录
  • env:Object,环境变量键值对
  • stdio:Array|String,子进程的stdio配置
  • detached:Boolean,这个子进程将会变成进程组的领导
  • uid:Number,设置用户进程的ID
  • gid:Number,设置进程组的ID

spawn()方法返回流 (stdout & stderr),在进程返回大量数据时使用。进程开始执行spawn()时就开始接收响应。

实例

在这个实例中我们创建两个js文件support.js和master.js。

support.js文件代码:

console.log("进程 " + process.argv[2] + " 执行。" );

master.js文件代码:

const fs = require('fs');const child_process = require('child_process'); for(var i=0; i<3; i++) {   var workerProcess = child_process.spawn('node', ['support.js', i]);   workerProcess.stdout.on('data', function (data) {      console.log('stdout: ' + data);   });   workerProcess.stderr.on('data', function (data) {      console.log('stderr: ' + data);   });   workerProcess.on('close', function (code) {      console.log('子进程已退出,退出码 '+code);   });}

执行以上代码,输出结果为:

$ node master.js stdout: 进程 0 执行。子进程已退出,退出码 0stdout: 进程 1 执行。子进程已退出,退出码 0stdout: 进程 2 执行。子进程已退出,退出码 0

fork 方法

child_process.fork是spawn()方法的特殊形式,用于创建进程,语法格式如下:

child_process.fork(modulePath[, args][, options])

参数

参数说明如下:

modulePath: String,将要在子进程中运行的模块

args: Array,字符串参数数组

options:Object

  • cwd:String,子进程的当前工作目录
  • env:Object,环境变量键值对
  • execPath:String,创建子进程的可执行文件
  • execArgv:Array,子进程的可执行文件的字符串参数数组(默认: process.execArgv)
  • silent:Boolean,如果为true,子进程的stdinstdoutstderr将会被关联至父进程,否则,它们将会从父进程中继承。(默认为:false
  • uid:Number,设置用户进程的ID
  • gid:Number,设置进程组的ID

返回的对象除了拥有ChildProcess实例的所有方法,还有一个内建的通信信道。

实例

让我们创建两个js文件support.js和master.js。

support.js文件代码如下所示:

console.log("进程 " + process.argv[2] + " 执行。" );

master.js文件代码如下所示:

const fs = require('fs');const child_process = require('child_process'); for(var i=0; i<3; i++) {   var worker_process = child_process.fork("support.js", [i]);	   worker_process.on('close', function (code) {      console.log('子进程已退出,退出码 ' + code);   });}

执行以上代码,输出结果为:

$ node master.js 进程 0 执行。子进程已退出,退出码 0进程 1 执行。子进程已退出,退出码 0进程 2 执行。子进程已退出,退出码 0


Node.js是一个开放源代码、跨平台的、用于服务器端和网络应用的运行环境。

JXcore是一个支持多线程的 Node.js 发行版本,基本不需要对你现有的代码做任何改动就可以直接线程安全地以多线程运行。

本文主要介绍JXcore的打包功能。


JXcore 安装

下载JXcore安装包,然后进行解压,在解压的目录下提供了jx二进制文件命令,接下来我们主要使用这个命令。

步骤1、下载

在 https://github.com/jxcore/jxcore-release 中下载JXcore安装包,你需要根据你自己的系统环境来下载安装包:

1、Window系统下载:Download

2、Linux/OSX下载安装命令,直接下载解压包下的jx二进制文件,然后拷贝到/usr/bin目录下:

$ wget https://s3.amazonaws.com/nodejx/jx_rh64.zip$ unzip jx_rh64.zip$ cp jx_rh64/jx /usr/bin

将/usr/bin添加到PATH路径中:

$ export PATH=$PATH:/usr/bin

以上步骤如果操作正确,使用以下命令,会输出版本号信息:

$ jx --versionv0.10.32

包代码

例如,我们的Node.js项目包含以下几个文件,其中index.js是主文件:

drwxr-xr-x  2 root root  4096 Nov 13 12:42 images-rwxr-xr-x  1 root root 30457 Mar  6 12:19 index.htm-rwxr-xr-x  1 root root 30452 Mar  1 12:54 index.jsdrwxr-xr-x 23 root root  4096 Jan 15 03:48 node_modulesdrwxr-xr-x  2 root root  4096 Mar 21 06:10 scriptsdrwxr-xr-x  2 root root  4096 Feb 15 11:56 style

接下来我们使用jx命令打包以上项目,并指定index.js为Node.js项目的主文件:

$ jx package index.js index

以上命令执行成功,会生成以下两个文件:

  • index.jxp:这是一个中间件文件,包含了需要编译的完整项目信息。

  • index.jx:这是一个完整包信息的二进制文件,可运行在客户端上。


载入 JX 文件

我们使用jx命令打包项目:

$ node index.js command_line_arguments

使用JXcore编译后,我们可以使用以下命令来执行生成的jx二进制文件:

$ jx index.jx command_line_arguments

更多JXcore功能特性你可以参考官网:http://jxcore.com/


本章节我们将为大家介绍如何使用 Node.js 来连接 MySQL,并对数据库进行操作。

如果你还没有 MySQL 的基本知识,可以参考我们的教程:MySQL 教程

本教程使用到的 Websites 表 SQL 文件:websites.sql

安装驱动

本教程使用了淘宝定制的 cnpm 命令进行安装:

$ cnpm install mysql

连接数据库

在以下实例中根据你的实际配置修改数据库用户名、及密码及数据库名:

test.js 文件代码:

var mysql      = require('mysql');var connection = mysql.createConnection({  host     : 'localhost',  user     : 'root',  password : '123456',  database : 'test'}); connection.connect(); connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) {  if (error) throw error;  console.log('The solution is: ', results[0].solution);});

执行以下命令输出结果为:

$ node test.jsThe solution is: 2

数据库连接参数说明:

参数描述
host主机地址 (默认:localhost)
  user用户名
  password密码
  port端口号 (默认:3306)
  database数据库名
  charset连接字符集(默认:'UTF8_GENERAL_CI',注意字符集的字母都要大写)
  localAddress此IP用于TCP连接(可选)
  socketPath连接到unix域路径,当使用 host 和 port 时会被忽略
  timezone时区(默认:'local')
  connectTimeout连接超时(默认:不限制;单位:毫秒)
  stringifyObjects是否序列化对象
  typeCast是否将列值转化为本地JavaScript类型值 (默认:true)
  queryFormat自定义query语句格式化方法
  supportBigNumbers数据库支持bigint或decimal类型列时,需要设此option为true (默认:false)
  bigNumberStringssupportBigNumbers和bigNumberStrings启用 强制bigint或decimal列以JavaScript字符串类型返回(默认:false)
  dateStrings强制timestamp,datetime,data类型以字符串类型返回,而不是JavaScript Date类型(默认:false)
  debug开启调试(默认:false)
  multipleStatements是否许一个query中有多个MySQL语句 (默认:false)
  flags用于修改连接标志
  ssl使用ssl参数(与crypto.createCredenitals参数格式一至)或一个包含ssl配置文件名称的字符串,目前只捆绑Amazon RDS的配置文件

更多说明可参见:https://github.com/mysqljs/mysql

数据库操作( CURD )

在进行数据库操作前,你需要将本站提供的 Websites 表 SQL 文件websites.sql 导入到你的 MySQL 数据库中。

本教程测试的 MySQL 用户名为 root,密码为 123456,数据库为 test,你需要根据自己配置情况修改。

查询数据

将上面我们提供的 SQL 文件导入数据库后,执行以下代码即可查询出数据:

查询数据

var mysql  = require('mysql');   var connection = mysql.createConnection({       host     : 'localhost',         user     : 'root',                password : '123456',         port: '3306',                     database: 'test' });  connection.connect(); var  sql = 'SELECT * FROM websites';//查connection.query(sql,function (err, result) {        if(err){          console.log('[SELECT ERROR] - ',err.message);          return;        }        console.log('--------------------------SELECT----------------------------');       console.log(result);       console.log('------------------------------------------------------------

');  }); connection.end();

执行以下命令输出就结果为:

$ node test.js--------------------------SELECT----------------------------[ RowDataPacket {    id: 1,    name: 'Google',    url: 'https://www.google.cm/',    alexa: 1,    country: 'USA' },  RowDataPacket {    id: 2,    name: '淘宝',    url: 'https://www.taobao.com/',    alexa: 13,    country: 'CN' },  RowDataPacket {    id: 3,    name: '编程狮',    url: 'http://www.51coolma.cn/',
alexa: 4689, country: 'CN' }, RowDataPacket { id: 4, name: '微博', url: 'http://weibo.com/', alexa: 20, country: 'CN' }, RowDataPacket { id: 5, name: 'Facebook', url: 'https://www.facebook.com/', alexa: 3, country: 'USA' } ]------------------------------------------------------------

插入数据

我们可以向数据表 websties 插入数据:

插入数据

var mysql  = require('mysql');   var connection = mysql.createConnection({       host     : 'localhost',         user     : 'root',                password : '123456',         port: '3306',                     database: 'test' });  connection.connect(); var  addSql = 'INSERT INTO websites(Id,name,url,alexa,country) VALUES(0,?,?,?,?)';var  addSqlParams = ['编程狮在线工具, 'https://123.51coolma.cn/webtools','23453', 'CN'];
//增connection.query(addSql,addSqlParams,function (err, result) { if(err){ console.log('[INSERT ERROR] - ',err.message); return; } console.log('--------------------------INSERT----------------------------'); //console.log('INSERT ID:',result.insertId); console.log('INSERT ID:',result); console.log('----------------------------------------------------------------- '); }); connection.end();

执行以下命令输出就结果为:

$ node test.js--------------------------INSERT----------------------------INSERT ID: OkPacket {  fieldCount: 0,  affectedRows: 1,  insertId: 6,  serverStatus: 2,  warningCount: 0,  message: '',  protocol41: true,  changedRows: 0 }-----------------------------------------------------------------

更新数据

我们也可以对数据库的数据进行修改:

更新数据

var mysql  = require('mysql');  var connection = mysql.createConnection({       host     : 'localhost',         user     : 'root',                password : '123456',         port: '3306',                     database: 'test' });  connection.connect(); var modSql = 'UPDATE websites SET name = ?,url = ? WHERE Id = ?';var modSqlParams = ['编程狮', 'https://m.51coolma.cn',6];//改connection.query(modSql,modSqlParams,function (err, result) {   if(err){         console.log('[UPDATE ERROR] - ',err.message);         return;   }          console.log('--------------------------UPDATE----------------------------');  console.log('UPDATE affectedRows',result.affectedRows);  console.log('-----------------------------------------------------------------

');}); connection.end();

执行以下命令输出就结果为:

--------------------------UPDATE----------------------------UPDATE affectedRows 1-----------------------------------------------------------------

删除数据

我们可以使用以下代码来删除 id 为 6 的数据:

删除数据

var mysql  = require('mysql');   var connection = mysql.createConnection({       host     : 'localhost',         user     : 'root',                password : '123456',         port: '3306',                     database: 'test' });  connection.connect(); var delSql = 'DELETE FROM websites where id=6';//删connection.query(delSql,function (err, result) {        if(err){          console.log('[DELETE ERROR] - ',err.message);          return;        }                console.log('--------------------------DELETE----------------------------');       console.log('DELETE affectedRows',result.affectedRows);       console.log('-----------------------------------------------------------------

');  }); connection.end();

执行以下命令输出就结果为:

--------------------------DELETE----------------------------DELETE affectedRows 1-----------------------------------------------------------------





MongoDB是一种文档导向数据库管理系统,由C++撰写而成。

本章节我们将为大家介绍如何使用 Node.js 来连接 MongoDB,并对数据库进行操作。

如果你还没有 MongoDB 的基本知识,可以参考我们的教程:MongoDB 教程

安装驱动

本教程使用了淘宝定制的 cnpm 命令进行安装:

$ cnpm install mongodb

接下来我们来实现增删改查功能。

创建数据库

要在 MongoDB 中创建一个数据库,首先我们需要创建一个 MongoClient 对象,然后配置好指定的 URL 和 端口号。

如果数据库不存在,MongoDB 将创建数据库并建立连接。

创建连接

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/51coolma"; 
MongoClient.connect(url, function(err, db) { if (err) throw err; console.log("数据库已创建!"); db.close(); });

创建集合

我们可以使用 createCollection() 方法来创建集合:

创建集合

var MongoClient = require('mongodb').MongoClient; var url = 'mongodb://localhost:27017/51coolma'; 
MongoClient.connect(url, function (err, db) { if (err) throw err; console.log('数据库已创建'); var dbase = db.db("51coolma"); dbase.createCollection('site', function (err, res) { if (err) throw err; console.log("创建集合!"); db.close(); }); });

数据库操作( CURD )

与 MySQL 不同的是 MongoDB 会自动创建数据库和集合,所以使用前我们不需要手动去创建。

插入数据

以下实例我们连接数据库 51coolma的 site 表,并插入一条数据条数据,使用 insertOne():

插入一条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma"); var myobj = { name: "编程狮", url: "www.51coolma" };
dbo.collection("site").insertOne(myobj, function(err, res) { if (err) throw err; console.log("文档插入成功"); db.close(); }); });

执行以下命令输出就结果为:

$ node test.js文档插入成功

从输出结果来看,数据已插入成功。

我们也可以打开 MongoDB 的客户端查看数据,如:

> show dbsrunoob  0.000GB          # 自动创建了 51coolma数据库
> show tablessite # 自动创建了 site 集合(数据表)> db.site.find(){ "_id" : ObjectId("5a794e36763eb821b24db854"), "name" : "编程狮", "url" : "www.51coolma" }
>

如果要插入多条数据可以使用 insertMany():

插入多条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma");
var myobj = [ { name: '编程狮工具', url: 'https://c.51coolma.com', type: 'cn'},
{ name: 'Google', url: 'https://www.google.com', type: 'en'}, { name: 'Facebook', url: 'https://www.google.com', type: 'en'} ]; dbo.collection("site").insertMany(myobj, function(err, res) { if (err) throw err; console.log("插入的文档数量为: " + res.insertedCount); db.close(); }); });

res.insertedCount 为插入的条数。

查询数据

可以使用 find() 来查找数据, find() 可以返回匹配条件的所有数据。 如果未指定条件,find() 返回集合中的所有数据。

find()

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma"); 
dbo.collection("site"). find({}).toArray(function(err, result) { // 返回集合中所有数据 if (err) throw err; console.log(result); db.close(); }); });

以下实例检索 name 为 "编程狮" 的实例:

查询指定条件的数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma"); 
var whereStr = {"name":'编程狮'}; // 查询条件 dbo.collection("site").find(whereStr).toArray(function(err, result) { if (err) throw err; console.log(result); db.close(); }); });

执行以下命令输出就结果为:

[ { _id: 5a794e36763eb821b24db854,    name: '编程狮',    url: 'www.51coolma' } ]

更新数据

我们也可以对数据库的数据进行修改,以下实例将 name 为 "菜鸟教程" 的 url 改为 https://www.runoob.com:

更新一条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma");
var whereStr = {"name":'编程狮'}; // 查询条件 var updateStr = {$set: { "url" : "https://www.51coolma.cn" }};
dbo.collection("site").updateOne(whereStr, updateStr, function(err, res) { if (err) throw err; console.log("文档更新成功"); db.close(); }); });

执行成功后,进入 mongo 管理工具查看数据已修改:

> db.site.find().pretty(){    "_id" : ObjectId("5a794e36763eb821b24db854"),    "name" : "编程狮",    "url" : "https://www.51coolma.cn"     // 已修改为 https
}

如果要更新所有符合条的文档数据可以使用 updateMany():

更新多条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma"); 
var whereStr = {"type":'en'}; // 查询条件 var updateStr = {$set: { "url" : "https://www.51coolma.cn" }};
dbo.collection("site").updateMany(whereStr, updateStr, function(err, res) { if (err) throw err; console.log(res.result.nModified + " 条文档被更新"); db.close(); }); });

result.nModified 为更新的条数。

删除数据

以下实例将 name 为 "编程狮" 的数据删除 :

删除一条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma"); 
var whereStr = {"name":'编程狮'}; // 查询条件 dbo.collection("site").deleteOne(whereStr, function(err, obj) { if (err) throw err; console.log("文档删除成功"); db.close(); }); });

执行成功后,进入 mongo 管理工具查看数据已删除:

> db.site.find()> 

如果要删除多条语句可以使用 deleteMany() 方法

以下实例将 type 为 en 的所有数据删除 :

删除多条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma"); 
var whereStr = { type: "en" }; // 查询条件 dbo.collection("site").deleteMany(whereStr, function(err, obj) { if (err) throw err; console.log(obj.result.n + " 条文档被删除"); db.close(); }); });

obj.result.n 删除的条数。

排序

排序 使用 sort() 方法,该方法接受一个参数,规定是升序(1)还是降序(-1)。

例如:

{ type: 1 }  // 按 type 字段升序{ type: -1 } // 按 type 字段降序

按 type 升序排列:

排序

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma"); 
var mysort = { type: 1 }; dbo.collection("site").find().sort(mysort).toArray(function(err, result) { if (err) throw err; console.log(result); db.close(); }); });

查询分页

如果要设置指定的返回条数可以使用 limit() 方法,该方法只接受一个参数,指定了返回的条数。

limit():读取两条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma"); 
dbo.collection("site").find().limit(2).toArray(function(err, result) { if (err) throw err; console.log(result); db.close(); }); });

如果要指定跳过的条数,可以使用 skip() 方法。

skip(): 跳过前面两条数据,读取两条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma");
dbo.collection("site").find().skip(2).limit(2).toArray(function(err, result) { if (err) throw err; console.log(result); db.close(); }); });

连接操作

mongoDB 不是一个关系型数据库,但我们可以使用 $lookup 来实现左连接。

例如我们有两个集合数据分别为:

集合1:orders

[  { _id: 1, product_id: 154, status: 1 }]

集合2:products

[  { _id: 154, name: '笔记本电脑' },  { _id: 155, name: '耳机' },  { _id: 156, name: '台式电脑' }]

$lookup 实现左连接

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://127.0.0.1:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma");
dbo.collection('orders').aggregate([ { $lookup: { from: 'products', // 右集合 localField: 'product_id', // 左集合 join 字段 foreignField: '_id', // 右集合 join 字段 as: 'orderdetails' // 新生成字段(类型array) } } ]).toArray(function(err, res) { if (err) throw err; console.log(JSON.stringify(res)); db.close(); }); });

删除集合

我们可以使用 drop() 方法来删除集合:

drop()

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma"); 
// 删除 test 集合 dbo.collection("test").drop(function(err, delOK) { // 执行成功 delOK 返回 true,否则返回 false if (err) throw err; if (delOK) console.log("集合已删除"); db.close(); }); });

使用 Promise

Promise 是一个 ECMAScript 6 提供的类,目的是更加优雅地书写复杂的异步任务。

如果你还不了解 Promise,可以参考 JavaScript Promise

以下实例使用 Promise 创建集合:

实例

const MongoClient = require("mongodb").MongoClient; const url = "mongodb://localhost/51coolma";
MongoClient.connect(url).then((conn) => { console.log("数据库已连接"); var dbase = conn.db("51coolma");
dbase.createCollection("site").then((res) => { console.log("已创建集合"); }).catch((err) => { console.log("数据库操作错误"); }).finally(() => { conn.close(); }); }).catch((err) => { console.log("数据库连接失败"); });

Promise 数据操作

现在我们在一个程序中实现四个连续操作:增加 、查询 、更改 、删除。

实例

const MongoClient = require("mongodb").MongoClient; const url = "mongodb://localhost/"; MongoClient.connect(url).then((conn) => { console.log("数据库已连接"); const test = conn.db("testdb").collection("test"); // 增加 test.insertOne({ "site": "51coolma.cn" }).then((res) => { 
// 查询 return test.find().toArray().then((arr) => { console.log(arr); }); }).then(() => { // 更改 return test.updateMany({ "site": "51coolma.cn" }, { $set: { "site": "example.com" } });
}).then((res) => { // 查询 return test.find().toArray().then((arr) => { console.log(arr); }); }).then(() => { // 删除 return test.deleteMany({ "site": "example.com" }); }).then((res) => { // 查询 return test.find().toArray().then((arr) => { console.log(arr); }); }).catch((err) => { console.log("数据操作失败" + err.message); }).finally(() => { conn.close(); }); }).catch((err) => { console.log("数据库连接失败"); });

执行结果:

数据库已连接[ { _id: 5f1664966833e531d83d3ac6, site: '51coolma.cn' } ]
[ { _id: 5f1664966833e531d83d3ac6, site: 'example.com' } ][]

用异步函数实现相同的数据操作

实例

const MongoClient = require("mongodb").MongoClient; const url = "mongodb://localhost/"; async function dataOperate() { var conn = null; try { conn = await MongoClient.connect(url); console.log("数据库已连接"); const test = conn.db("testdb").collection("test"); // 增加 await test.insertOne({ "site": "51coolma.cn" }); 
// 查询 var arr = await test.find().toArray(); console.log(arr); // 更改 await test.updateMany({ "site": "51coolma.cn" }, { $set: { "site": "example.com" } }); // 查询 arr = await test.find().toArray(); console.log(arr); // 删除 await test.deleteMany({ "site": "example.com" }); // 查询 arr = await test.find().toArray(); console.log(arr); } catch (err) { console.log("错误:" + err.message); } finally { if (conn != null) conn.close(); } } dataOperate();

运行结果:

数据库已连接[ { _id: 5f169006a2780f0cd4ea640b, site: '51coolma.cn' } ][ { _id: 5f169006a2780f0cd4ea640b, site: 'example.com' } ][]

运行结果完全一样。

很显然,异步函数是一种非常良好的编程风格,在多次使用异步操作的时候非常实用。

但是请勿在低于 7.6.0 版本的 node.js 上使用异步函数。




本文档翻译自 Node.js 官方文档,适用于 V0.12.2。

本文档将从引用参考和概念两个方面来对Node.js API进行全面的解释,让你更加了解Node.js API。

Node.js官方文档的每个章节描述了一个内置模块或高级概念。

一般情况下,属性、方法参数,以及提供给事件处理程序的参数都会在主标题下的列表中详细说明。

每个.html文档都有对应的.json,它们包含相同的结构化内容。这些东西目前还是实验性的,主要为各种集成开发环境(IDE)和开发工具提供便利。

每个.html.json文件都和doc/api/目录下的.markdown文件相对应。这些文档使用tools/doc/generate.js程序生成。 HTML模板位于doc/template.html

稳定性标志

在文档中,你会看到每个章节的稳定性标志。Node.js API还在改进中,成熟的部分会比其他章节值得信赖。经过大量验证和依赖的API是一般是不会变的。其他新增的,试验性的,或被证明具有危险性的部分正被重新设计。

稳定性标志包括以下内容:

稳定性(Stability): 0 - 抛弃这部分内容有问题,并已计划改变。不要使用这些内容,可能会引起警告。不要想什么向后兼容性了。
稳定性(Stability): 1 - 试验这部分内容最近刚引进,将来的版本可能会改变也可能会被移除。你可以试试并提供反馈。如果你用到的部分对你来说非常重要,可以告诉 node 的核心团队。
稳定性(Stability): 2 - 不稳定这部分 API 正在调整中,还没在实际工作测试中达到满意的程度。如果合理的话会保证向后兼容性。
稳定性(Stability): 3 - 稳定这部分 API 验证过基本能令人满意,但是清理底层代码时可能会引起小的改变。保证向后的兼容性。
稳定性(Stability): 4 - API 冻结这部分的 API 已经在产品中广泛试验,不太可能被改变。
稳定性(Stability): 5 - 锁定除非发现了严重 bug,否则这部分代码永远不会改变。请不要对这部分内容提出更改建议,否则会被拒绝。

JSON 输出

稳定性(Stability): 1 - 试验

每个通过 markdown 生成的 HTML 文件都有相应的 JSON 文件。

这个特性从 v0.6.12 开始,是试验性功能。

更新日期更新内容
2015-04-21第一版发布,翻译自 Node.js V0.12.2 官方文档

相关教程

JSON教程


第一个服务器的例子就从 “Hello World” 开始:

var http = require('http');http.createServer(function (request, response) {  response.writeHead(200, {'Content-Type': 'text/plain'});  response.end('Hello World
');}).listen(8124);console.log('Server running at http://127.0.0.1:8124/');

把代码拷贝到example.js文件里,使用node程序执行:

> node example.jsServer running at http://127.0.0.1:8124/

在该Node.js官方文档中的所有的例子都可以使用上述方法执行。


Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。

稳定性: 5 - 锁定

这个模块可用于应用的单元测试,通过 require('assert') 可以使用这个模块。

assert.fail(actual, expected, message, operator)

使用参数operator测试参数actual (实际值) 和expected(期望值)是否相等。

assert(value[, message]), assert.ok(value[, message])

测试参数value是否为true,此函数和assert.equal(true, !!value, message);等价。

assert.equal(actual, expected[, message])

判断实际值actual和期望值expected是否相等。

assert.notEqual(actual, expected[, message])

判断实际值actual和期望值expected是否不等。

assert.deepEqual(actual, expected[, message])

执行深度比较,判断实际值actual和期望值expected是否相等。

assert.notDeepEqual(actual, expected[, message])

深度比较两个参数是否不相等。

assert.strictEqual(actual, expected[, message])

深度比较两个参数是否相等。

assert.notStrictEqual(actual, expected[, message])

此函数使用操作符 ‘!==’ 严格比较是否两参数不相等。

assert.throws(block[, error][, message])

声明一个block用来抛出错误(error),error可以是构造函数,正则表达式或其他验证器。

使用构造函数验证实例:

    assert.throws(      function() {        throw new Error("Wrong value");      },      Error    );

使用正则表达式验证错误信息:

    assert.throws(      function() {        throw new Error("Wrong value");      },      /value/    );

用户自定义的错误验证器:

    assert.throws(      function() {        throw new Error("Wrong value");      },      function(err) {        if ( (err instanceof Error) && /value/.test(err) ) {          return true;        }      },      "unexpected error"    );

assert.doesNotThrow(block[, message])

声明block不抛出错误,详细信息参见assert.throws

assert.ifError(value)

判断参数value是否为false ,如果是true,则抛出异常。通常用来测试回调中第一个参数error。


文档: 4 - API 冻结

Node里很多对象会分发事件: 每次有连接的时候net.Server会分发事件,当文件打开的时候fs.readStream会分发事件。所有能分发事件的对象都是 events.EventEmitter的实例。通过require("events");能访问这个模块。

一般来说,事件名都遵照驼峰规则,但这不是强制规定,任何形式的字符串都可以做为事件名。

为了处理事件,通常将函数关联到对象上。这些函数也叫监听者(listeners)。在这个函数里,this指向监听者所关联的EventEmitter

类: events.EventEmitter

你可以通过require('events').EventEmitter获取EventEmitter类。

EventEmitter实例遇到错误后,通常会触发一个错误事件。错误事件在node里是特殊例子。如果没有监听者,默认的操作是打印一个堆栈信息并退出程序。

当添加新的监听者时, EventEmitters会触发'newListener'事件,当移除时会触发'removeListener'

emitter.addListener(event, listener)

emitter.on(event, listener)

添加一个监听者到特定event的监听数组的尾部,触发器不会检查是否已经添加过这个监听者。 多次调用相同的eventlistener将会导致listener添加多次。

server.on('connection', function (stream) {  console.log('someone connected!');});

返回emitter。

emitter.once(event, listener)

给事件添加一个一次性的listener,这个listener只会被触发一次,之后就会被移除。

server.once('connection', function (stream) {  console.log('Ah, we have our first user!');});

返回emitter。

emitter.removeListener(event, listener)

从一个某个事件的listener数组中移除一个listener。注意,这个操作会改变listener数组内容的次序。

var callback = function(stream) {  console.log('someone connected!');};server.on('connection', callback);// ...server.removeListener('connection', callback);

removeListener最多会移除数组里的一个listener。如果多次添加同一个listener到数组,那就需要多次调用removeListener来移除每一个实例。

返回emitter。

emitter.removeAllListeners([event])

移除所有的listener,或者某个事件listener。最好不要移除全部listener,尤其是那些不是你传入的(比如socket或文件流)。

返回emitter。

emitter.setMaxListeners(n)

默认情况下,给单个事件添加超过10个listener,事件分发器会打印警告。这样有利于检查内存泄露。不过不是所有的分发器都应该限制在10个,这个函数允许改变 listener数量,无论是0还是更多。

返回emitter。

EventEmitter.defaultMaxListeners

emitter.setMaxListeners(n)设置一个分发器的最大listener数,而这个函数会立即设置所有EventEmitter的当前值和默认值。要小心使用。

请注意,emitter.setMaxListeners(n)的优先级高于EventEmitter.defaultMaxListeners.

emitter.listeners(event)

用于返回事件的listener数组。

server.on('connection', function (stream) {  console.log('someone connected!');});console.log(util.inspect(server.listeners('connection'))); // [ [Function] ]

emitter.emit(event[, arg1][, arg2][, ...])

允许你使用指定的参数顺序的执行每一个listener.

如果事件有 listener,返回true, 否则false

类方法: EventEmitter.listenerCount(emitter, event)

返回指定事件的listener数量。

Event: 'newListener'

  • event{String}事件名
  • listener{Function}事件处理函数

添加listener的时候会触发这个事件。当这个事件触发的时候,listener可能还没添加到listener数组。

Event: 'removeListener'

  • event{String}事件名
  • listener{Function}事件处理函数

删除listener的时候会触发这个事件。当这个事件触发的时候,listener可能还还没从listener数组移除。


Punycode是根据RFC 3492标准定义的字符编码方案,主要用于把域名从地方语言所采用的Unicode编码转换成为可用于DNS系统的编码。

稳定性: 2 - 不稳定

Punycode.js从Node.js v0.6.2+开始内置. 使用require('punycode')来访问。 (要在其他Node.js版本中访问,先用npm来punycode安装)。

punycode.decode(string)

将一个纯ASCII的Punycode字符串转换为Unicode字符串。

// decode domain name partspunycode.decode('maana-pta'); // 'mañana'punycode.decode('--dqo34k'); // '☃-⌘'

punycode.encode(string)

将一个纯Unicode Punycode字符串转换为纯ASCII字符串。

// encode domain name partspunycode.encode('mañana'); // 'maana-pta'punycode.encode('☃-⌘'); // '--dqo34k'

punycode.toUnicode(domain)

将一个表示域名的Punycode字符串转换为Unicode。只有域名中的Punycode部分会被转换,也就是说你也可以在一个已经转换为Unicode的字符串上调用它。

// decode domain namespunycode.toUnicode('xn--maana-pta.com'); // 'mañana.com'punycode.toUnicode('xn----dqo34k.com'); // '☃-⌘.com'

punycode.toASCII(domain)

将一个表示域名的Unicode字符串转换为Punycode。只有域名中的非ASCII部分会被转换,也就是说你也可以在一个已经转换为ASCII的字符串上调用它。

// encode domain namespunycode.toASCII('mañana.com'); // 'xn--maana-pta.com'punycode.toASCII('☃-⌘.com'); // 'xn----dqo34k.com'

punycode.ucs2

punycode.ucs2.decode(string)

创建一个包含字符串中每个Unicode符号的数字编码点的数组。由于JavaScript在内部使用UCS-2, 该函数会按照UTF-16将一对代半数(UCS-2暴露的单独的字符)转换为单独一个编码点。

punycode.ucs2.decode('abc'); // [0x61, 0x62, 0x63]// surrogate pair for U+1D306 tetragram for centre:punycode.ucs2.decode('uD834uDF06'); // [0x1D306]

punycode.ucs2.encode(codePoints)

创建以一组数字编码点为基础一个字符串。

punycode.ucs2.encode([0x61, 0x62, 0x63]); // 'abc'punycode.ucs2.encode([0x1D306]); // 'uD834uDF06'

punycode.version

表示当前Punycode.js版本数字的字符串。


稳定性: 3 - 稳定

纯Javascript语言对Unicode友好,能够很好地处理Unicode编码的字符串数据,但是难以处理二进制数据。在处理TCP流和文件系统时经常需要操作字节流。Node提供了一些机制,用于操作、创建、以及消耗字节流。

在Node.js中提供了Buffer,它可以处理二进制以及非Unicode编码的数据。

在Buffer类实例化中存储了原始数据。Buffer类似于一个整数数组,在V8堆(the V8 heap)原始存储空间给它分配了内存。一旦创建了Buffer实例,则无法改变大小。

Buffer类是全局对象,所以访问它不必使用require('buffer')

Buffers和Javascript字符串对象之间转换时需要一个明确的编码方法。下面是字符串的不同编码:

  • 'ascii'- 7位的ASCII数据。这种编码方式非常快,它会移除最高位内容。

  • 'utf8'- 多字节编码Unicode字符。大部分网页和文档使用这类编码方式。

  • 'utf16le'- 2个或4个字节, Little Endian (LE)编码Unicode字符。编码范围(U+10000 到 U+10FFFF) 。

  • 'ucs2'-'utf16le'的子集。

  • 'base64' - Base64字符编码。

  • 'binary'- 仅使用每个字符的头8位将原始的二进制信息进行编码。在需使用Buffer的情况下,应该尽量避免使用这个已经过时的编码方式。这个编码方式将会在未来某个版本中弃用。
  • 'hex'- 每个字节都采用二进制编码。

Buffer中创建一个数组,需要注意以下规则:

  1. Buffer是内存拷贝,而不是内存共享。

  2. Buffer占用内存被解释为一个数组,而不是字节数组。比如,new Uint32Array(new Buffer([1,2,3,4]))创建了4个Uint32Array,它的成员为 [1,2,3,4],而不是[0x1020304][0x4030201]

注意:Node.js v0.8只是简单的引用了array.buffer里的buffer,而不是对其进行克隆(cloning)。

介绍一个高效的方法,ArrayBuffer#slice()拷贝了一份切片,而Buffer#slice()新建了一份。

类: Buffer

Buffer类是全局变量类型,用来直接处理二进制数据。它能够使用多种方式构建。

new Buffer(size)

  • sizeNumber类型

分配一个新的size大小单位为8位字节的buffer。

注意:size必须小于kMaxLength,否则将会抛出RangeError异常。

new Buffer(array)

  • arrayArray

使用一个8位字节array数组分配一个新的buffer。

new Buffer(buffer)

  • buffer{Buffer}

拷贝参数buffer的数据到Buffer实例。

new Buffer(str[, encoding])

  • str String类型 - 需要编码的字符串。
  • encoding String类型 - 编码方式, 可选。

分配一个新的buffer ,其中包含着传入的str字符串。encoding 编码方式默认为'utf8'

类方法: Buffer.isEncoding(encoding)

  • encoding {String} 用来测试给定的编码字符串

如果参数编码encoding是有效的,则返回true,否则返回false。

类方法: Buffer.isBuffer(obj)

  • obj对象
  • 返回:Boolean

obj如果是Buffer 返回true,否则返回 false。

类方法: Buffer.byteLength(string[, encoding])

  • string String类型
  • encoding String类型,可选的,默认为: 'utf8'
  • 返回:Number类型

将会返回这个字符串真实字节长度。 encoding编码默认是:utf8。 这和String.prototype.length不一样,因为那个方法返回这个字符串中字符的数量。

例如:

str = 'u00bd + u00bc = u00be';console.log(str + ": " + str.length + " characters, " +  Buffer.byteLength(str, 'utf8') + " bytes");// ½ + ¼ = ¾: 9 characters, 12 bytes

类方法: Buffer.concat(list[, totalLength])

  • list {Array} 用来连接的数组
  • totalLength {Number 类型} 数组里所有对象的总buffer大小

返回一个buffer对象,它将参数buffer数组中所有buffer对象拼接在一起。

如果传入的数组没有内容,或者totalLength是0,那将返回一个长度为0的buffer。

如果数组长度为1,返回数组第一个成员。

如果数组长度大于0,将会创建一个新的Buffer实例。

如果没有提供totalLength参数,会根据buffer数组计算,这样会增加一个额外的循环计算,所以提供一个准确的totalLength参数速度更快。

类方法: Buffer.compare(buf1, buf2)

  • buf1 {Buffer}
  • buf2 {Buffer}

buf1.compare(buf2)一样, 用来对数组排序:

var arr = [Buffer('1234'), Buffer('0123')];arr.sort(Buffer.compare);

buf.length

  • Number类型

返回这个buffer的bytes数。注意这未必是buffer里面内容的大小。length是buffer对象所分配的内存数,它不会随着这个buffer对象内容的改变而改变。

buf = new Buffer(1234);console.log(buf.length);buf.write("some string", 0, "ascii");console.log(buf.length);// 1234// 1234

length不能改变,如果改变length将会导致不可以预期的结果。如果想要改变buffer的长度,需要使用buf.slice来创建新的buffer。

buf = new Buffer(10);buf.write("abcdefghj", 0, "ascii");console.log(buf.length); // 10buf = buf.slice(0,5);console.log(buf.length); // 5

buf.write(string[, offset][, length][, encoding])

  • string String类型 - 写到buffer里
  • offset Number类型,可选参数,默认值: 0
  • length Number类型,可选参数,默认值:buffer.length - offset
  • encoding String类型,可选参数,默认值: 'utf8'

根据参数offset偏移量和指定的encoding编码方式,将参数string数据写入buffer。offset偏移量默认值是0,encoding编码方式默认是utf8。 length长度是将要写入的字符串的bytes大小。返回number类型,表示写入了多少8位字节流。如果buffer没有足够的空间来放整个string,它将只会只写入部分字符串。length默认是 buffer.length - offset。 这个方法不会出现写入部分字符。

buf = new Buffer(256);len = buf.write('u00bd + u00bc = u00be', 0);console.log(len + " bytes: " + buf.toString('utf8', 0, len));

buf.writeUIntLE(value, offset, byteLength[, noAssert])

buf.writeUIntBE(value, offset, byteLength[, noAssert])

buf.writeIntLE(value, offset, byteLength[, noAssert])

buf.writeIntBE(value, offset, byteLength[, noAssert])

  • value {Number 类型}准备写到buffer字节数
  • offset {Number 类型} 0 <= offset <= buf.length
  • byteLength {Number 类型} 0 < byteLength <= 6
  • noAssert {Boolean} 默认值: false
  • 返回: {Number 类型}

value写入到buffer里, 它由offsetbyteLength决定,支持48位计算,例如:

var b = new Buffer(6);b.writeUIntBE(0x1234567890ab, 0, 6);// <Buffer 12 34 56 78 90 ab>

noAssert值为true时,不再验证valueoffset 的有效性。 默认是false

buf.readUIntLE(offset, byteLength[, noAssert])

buf.readUIntBE(offset, byteLength[, noAssert])

buf.readIntLE(offset, byteLength[, noAssert])

buf.readIntBE(offset, byteLength[, noAssert])

  • offset {Number 类型} 0 <= offset <= buf.length
  • byteLength {Number 类型} 0 < byteLength <= 6
  • noAssert {Boolean} 默认值: false
  • 返回: {Number 类型}

支持48位以下的数字读取。 例如:

var b = new Buffer(6);b.writeUint16LE(0x90ab, 0);b.writeUInt32LE(0x12345678, 2);b.readUIntLE(0, 6).toString(16);  // 指定为 6 bytes (48 bits)// 输出: '1234567890ab'

noAssert 值为true时, offset不再验证是否超过buffer的长度,默认为false

buf.toString([encoding][, start][, end])

  • encoding String 类型,可选参数,默认值: 'utf8'
  • start Number 类型,可选参数,默认值: 0
  • end Number 类型,可选参数,默认值: buffer.length

根据encoding参数(默认是 'utf8')返回一个解码过的string类型。还会根据传入的参数start(默认是 0)和end (默认是 buffer.length)作为取值范围。

buf = new Buffer(26);for (var i = 0 ; i < 26 ; i++) {  buf[i] = i + 97; // 97 is ASCII a}buf.toString('ascii'); // 输出: abcdefghijklmnopqrstuvwxyzbuf.toString('ascii',0,5); // 输出: abcdebuf.toString('utf8',0,5); // 输出: abcdebuf.toString(undefined,0,5); // encoding defaults to 'utf8', 输出 abcde

查看上面buffer.write()例子。

buf.toJSON()

返回一个JSON表示的Buffer实例。JSON.stringify 将会默认调用字符串序列化这个Buffer实例。

例如:

var buf = new Buffer('test');var json = JSON.stringify(buf);console.log(json);// '{"type":"Buffer","data":[116,101,115,116]}'var copy = JSON.parse(json, function(key, value) {    return value && value.type === 'Buffer'      ? new Buffer(value.data)      : value;  });console.log(copy);// <Buffer 74 65 73 74>

buf[index]

获取或设置指定index位置的8位字节。这个值是指单个字节,所以必须在合法的范围取值,16进制的0x00到0xFF,或者0到255。

例如: 拷贝一个ASCII编码的string字符串到一个buffer,一次一个byte进行拷贝:

str = "node.js";buf = new Buffer(str.length);for (var i = 0; i < str.length ; i++) {  buf[i] = str.charCodeAt(i);}console.log(buf);// node.js

buf.equals(otherBuffer)

  • otherBuffer {Buffer}

如果 thisotherBuffer 拥有相同的内容,返回true。

buf.compare(otherBuffer)

  • otherBuffer {Buffer}

返回一个数字,表示 thisotherBuffer 之前,之后或相同。

buf.copy(targetBuffer[, targetStart][, sourceStart][, sourceEnd])

  • targetBuffer Buffer 对象 - Buffer to copy into
  • targetStart Number 类型,可选参数, 默认值: 0
  • sourceStart Number 类型,可选参数, 默认值: 0
  • sourceEnd Number 类型,可选参数, 默认值: buffer.length

buffer拷贝,源和目标可以相同。targetStart目标开始偏移和sourceStart源开始偏移默认都是0。sourceEnd源结束位置偏移默认是源的长度 buffer.length

例如:创建2个Buffer,然后把buf1的16到19位内容拷贝到buf2第8位之后。

buf1 = new Buffer(26);buf2 = new Buffer(26);for (var i = 0 ; i < 26 ; i++) {  buf1[i] = i + 97; // 97 is ASCII a  buf2[i] = 33; // ASCII !}buf1.copy(buf2, 8, 16, 20);console.log(buf2.toString('ascii', 0, 25));// !!!!!!!!qrst!!!!!!!!!!!!!

例如: 在同一个buffer中,从一个区域拷贝到另一个区域:

buf = new Buffer(26);for (var i = 0 ; i < 26 ; i++) {  buf[i] = i + 97; // 97 is ASCII a}buf.copy(buf, 0, 4, 10);console.log(buf.toString());// efghijghijklmnopqrstuvwxyz

buf.slice([start][, end])

  • start Number类型,可选参数,默认值: 0
  • end Number类型,可选参数,默认值: buffer.length

返回一个新的buffer,这个buffer将会和老的buffer引用相同的内存地址,根据start(默认是 0 ) 和end (默认是 buffer.length ) 偏移和裁剪了索引。 负的索引是从buffer尾部开始计算的。

修改这个新的buffer实例slice切片,也会改变原来的buffer!

例如: 创建一个ASCII字母的Buffer,进行slice切片,然后修改源Buffer上的一个byte。

var buf1 = new Buffer(26);for (var i = 0 ; i < 26 ; i++) {  buf1[i] = i + 97; // 97 is ASCII a}var buf2 = buf1.slice(0, 3);console.log(buf2.toString('ascii', 0, buf2.length));buf1[0] = 33;console.log(buf2.toString('ascii', 0, buf2.length));// abc// !bc

buf.readUInt8(offset[, noAssert])

  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false
  • 返回: Number类型

从这个buffer对象里,根据指定的偏移量,读取一个有符号8位整数 整形。

若参数noAssert为true将不会验证offset偏移量参数。 如果这样offset 可能会超出buffer的末尾。默认是false

例如:

var buf = new Buffer(4);buf[0] = 0x3;buf[1] = 0x4;buf[2] = 0x23;buf[3] = 0x42;for (ii = 0; ii < buf.length; ii++) {  console.log(buf.readUInt8(ii));}// 0x3// 0x4// 0x23// 0x42

buf.readUInt16LE(offset[, noAssert])

buf.readUInt16BE(offset[, noAssert])

  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false
  • 返回: Number类型

从buffer对象里,根据指定的偏移量,使用特殊的endian字节序格式读取一个有符号16位整数。

若参数noAssert为true将不会验证offset偏移量参数。 这意味着offset可能会超出buffer的末尾。默认是false

例如:

var buf = new Buffer(4);buf[0] = 0x3;buf[1] = 0x4;buf[2] = 0x23;buf[3] = 0x42;console.log(buf.readUInt16BE(0));console.log(buf.readUInt16LE(0));console.log(buf.readUInt16BE(1));console.log(buf.readUInt16LE(1));console.log(buf.readUInt16BE(2));console.log(buf.readUInt16LE(2));// 0x0304// 0x0403// 0x0423// 0x2304// 0x2342// 0x4223

buf.readUInt32LE(offset[, noAssert])

buf.readUInt32BE(offset[, noAssert])

  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false
  • 返回: Number类型

从这个buffer对象里,根据指定的偏移量,使用指定的endian字节序格式读取一个有符号32位整数。

若参数noAssert为true将不会验证offset偏移量参数。 这意味着offset可能会超出buffer的末尾。默认是false

例如:

var buf = new Buffer(4);buf[0] = 0x3;buf[1] = 0x4;buf[2] = 0x23;buf[3] = 0x42;console.log(buf.readUInt32BE(0));console.log(buf.readUInt32LE(0));// 0x03042342// 0x42230403

buf.readInt8(offset[, noAssert])

  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false
  • 返回: Number类型

从这个buffer对象里,根据指定的偏移量,读取一个signed 8位整数。

若参数noAssert为true将不会验证offset偏移量参数。 这意味着offset可能会超出buffer的末尾。默认是false

返回和buffer.readUInt8一样,除非buffer中包含了有作为2的补码的有符号值。

buf.readInt16LE(offset[, noAssert])

buf.readInt16BE(offset[, noAssert])

  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false
  • 返回: Number类型

从这个buffer对象里,根据指定的偏移量,使用特殊的endian格式读取一个signed 16位整数。

若参数noAssert为true将不会验证 offset 偏移量参数。 这意味着offset可能会超出buffer的末尾。默认是false

返回和buffer.readUInt16一样,除非buffer中包含了有作为2的补码的有符号值。

buf.readInt32LE(offset[, noAssert])

buf.readInt32BE(offset[, noAssert])

  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false
  • 返回: Number类型

从这个buffer对象里,根据指定的偏移量,使用指定的endian字节序格式读取一个signed 32位整数。

若参数noAssert为true将不会验证offset偏移量参数。 这意味着offset可能会超出buffer的末尾。默认是false

buffer.readUInt32一样返回,除非buffer中包含了有作为2的补码的有符号值。

buf.readFloatLE(offset[, noAssert])

buf.readFloatBE(offset[, noAssert])

  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false
  • 返回: Number类型

从这个buffer对象里,根据指定的偏移量,使用指定的endian字节序格式读取一个32位浮点数。

若参数noAssert为true将不会验证offset偏移量参数。 这意味着offset可能会超出buffer的末尾。默认是false

例如:

var buf = new Buffer(4);buf[0] = 0x00;buf[1] = 0x00;buf[2] = 0x80;buf[3] = 0x3f;console.log(buf.readFloatLE(0));// 0x01

buf.readDoubleLE(offset[, noAssert])

buf.readDoubleBE(offset[, noAssert])

  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false
  • 返回: Number类型

从这个buffer对象里,根据指定的偏移量,使用指定的endian字节序格式读取一个64位double。

若参数noAssert为true将不会验证offset偏移量参数。 这意味着offset可能会超出buffer 的末尾。默认是false

例如:

var buf = new Buffer(8);buf[0] = 0x55;buf[1] = 0x55;buf[2] = 0x55;buf[3] = 0x55;buf[4] = 0x55;buf[5] = 0x55;buf[6] = 0xd5;buf[7] = 0x3f;console.log(buf.readDoubleLE(0));// 0。3333333333333333

buf.writeUInt8(value, offset[, noAssert])

  • value Number类型
  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false

根据传入的offset偏移量将value写入buffer。注意:value必须是一个合法的有符号 8 位整数。

若参数noAssert为true将不会验证offset 偏移量参数。 这意味着value可能过大,或者offset可能会超出buffer的末尾从而造成value被丢弃。 除非你对这个参数非常有把握,否则不要使用。默认是false

例如:

var buf = new Buffer(4);buf.writeUInt8(0x3, 0);buf.writeUInt8(0x4, 1);buf.writeUInt8(0x23, 2);buf.writeUInt8(0x42, 3);console.log(buf);// <Buffer 03 04 23 42>

buf.writeUInt16LE(value, offset[, noAssert])

buf.writeUInt16BE(value, offset[, noAssert])

  • value Number类型
  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false

根据传入的offset偏移量和指定的endian格式将value写入buffer。注意:value必须是一个合法的有符号16位整数。

若参数noAssert为true将不会验证valueoffset偏移量参数。 这意味着value可能过大,或者offset可能会超出buffer的末尾从而造成value被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是false

例如:

var buf = new Buffer(4);buf.writeUInt16BE(0xdead, 0);buf.writeUInt16BE(0xbeef, 2);console.log(buf);buf.writeUInt16LE(0xdead, 0);buf.writeUInt16LE(0xbeef, 2);console.log(buf);// <Buffer de ad be ef>// <Buffer ad de ef be>

buf.writeUInt32LE(value, offset[, noAssert])

buf.writeUInt32BE(value, offset[, noAssert])

  • value Number类型
  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false

根据传入的offset偏移量和指定的endian格式将value写入buffer。注意:value必须是一个合法的有符号32位整数。

若参数noAssert为true将不会验证valueoffset偏移量参数。 这意味着value可能过大,或者offset可能会超出buffer的末尾从而造成value被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是false

例如:

var buf = new Buffer(4);buf.writeUInt32BE(0xfeedface, 0);console.log(buf);buf.writeUInt32LE(0xfeedface, 0);console.log(buf);// <Buffer fe ed fa ce>// <Buffer ce fa ed fe>

buf.writeInt8(value, offset[, noAssert])

  • value Number类型
  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false

根据传入的offset偏移量将value写入buffer。注意:value必须是一个合法的signed 8位整数。

若参数noAssert为true将不会验证valueoffset偏移量参数。这意味着value可能过大,或者offset可能会超出buffer的末尾从而造成value被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是false

buffer.writeUInt8一样工作,除非是把有2的补码的有符号整数有符号整形写入buffer。

buf.writeInt16LE(value, offset[, noAssert])

buf.writeInt16BE(value, offset[, noAssert])

  • value Number类型
  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false

根据传入的offset偏移量和指定的endian格式将value写入buffer。注意:value必须是一个合法的signed 16位整数。

若参数noAssert为true将不会验证valueoffset偏移量参数。 这意味着value可能过大,或者offset可能会超出buffer的末尾从而造成value被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是false

buffer.writeUInt16*一样工作,除非是把有2的补码的有符号整数 有符号整形写入buffer

buf.writeInt32LE(value, offset[, noAssert])

buf.writeInt32BE(value, offset[, noAssert])

  • value Number类型
  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false

根据传入的offset偏移量和指定的endian格式将value写入buffer。注意:value必须是一个合法的signed 32位整数。

若参数noAssert为true将不会验证valueoffset偏移量参数。 这意味着value可能过大,或者offset可能会超出 buffer 的末尾从而造成value被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是false

buffer.writeUInt32*一样工作,除非是把有2的补码的有符号整数、有符号整形写入buffer。

buf.writeFloatLE(value, offset[, noAssert])

buf.writeFloatBE(value, offset[, noAssert])

  • value Number类型
  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false

根据传入的offset偏移量和指定的endian格式将value写入buffer。注意:当value不是一个32位浮点数类型的值时,结果将是不确定的。

若参数noAssert为true将不会验证valueoffset偏移量参数。这意味着value可能过大,或者offset可能会超出buffer的末尾从而造成value被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是false

例如:

var buf = new Buffer(4);buf.writeFloatBE(0xcafebabe, 0);console.log(buf);buf.writeFloatLE(0xcafebabe, 0);console.log(buf);// <Buffer 4f 4a fe bb>// <Buffer bb fe 4a 4f>

buf.writeDoubleLE(value, offset[, noAssert])

buf.writeDoubleBE(value, offset[, noAssert])

  • value Number类型
  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false

根据传入的offset偏移量和指定的endian格式将value写入buffer。注意:value必须是一个有效的64位double类型的值。

若参数noAssert为true将不会验证valueoffset偏移量参数。 这意味着value可能过大,或者offset可能会超出buffer的末尾从而造成value被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是false

例如:

var buf = new Buffer(8);buf.writeDoubleBE(0xdeadbeefcafebabe, 0);console.log(buf);buf.writeDoubleLE(0xdeadbeefcafebabe, 0);console.log(buf);// <Buffer 43 eb d5 b7 dd f9 5f d7>// <Buffer d7 5f f9 dd b7 d5 eb 43>

buf.fill(value[, offset][, end])

  • value
  • offset Number类型, Optional
  • end Number类型, Optional

使用指定的value来填充这个buffer。如果没有指定offset (默认是 0) 并且end(默认是buffer.length) ,将会填充整个buffer。

var b = new Buffer(50);b.fill("h");

buffer.iNSPECT_MAX_BYTES

  • Number 类型,默认值: 50

设置当调用buffer.inspect()方法后,将会返回多少bytes 。用户模块重写这个值可以。

注意这个属性是require('buffer')模块返回的。这个属性不是在全局变量Buffer中,也不在buffer的实例里。

类: SlowBuffer

返回一个不被池管理的Buffer

大量独立分配的Buffer容易带来垃圾,为了避免这个情况,小于4KB的空间都是切割自一个较大的独立对象。这种策略既提高了性能也改善了内存使用率。V8不需要跟踪和清理过多的Persistent对象。

当开发者需要将池中一小块数据保留一段时间,比较好的办法是用SlowBuffer创建一个不被池管理的Buffer实例,并将相应数据拷贝出来,如下所示:

// need to keep around a few small chunks of memoryvar store = [];socket。on('readable', function() {  var data = socket。read();  // allocate for retained data  var sb = new SlowBuffer(10);  // copy the data into the new allocation  data。copy(sb, 0, 0, 10);  store。push(sb);});

请谨慎使用,仅作为经常发现他们的应用中过度的内存保留时的最后手段。


Node.js官方文档对Node.js文件系统进行了详细的介绍。

稳定性: 3 - 稳定

Node.js文件系统模块是一个封装了标准的POSIX文件I/O操作的集合。通过require('fs')使用这个模块,其中所有的方法都有同步和异步两种模式。

异步方法最后一个参数都是回调函数,这个回调的参数取决于方法,不过第一个参数一般都是异常。如果操作成功,那么第一个参数就是nullundefined

当使用一个同步操作的时候,任意的异常都立即抛出,可以用try/catch来处理异常,使得程序正常运行。

以下是一个异步操作的例子:

var fs = require('fs');fs.unlink('/tmp/hello', function (err) {  if (err) throw err;  console.log('successfully deleted /tmp/hello');});

以下是一个同步操作的例子:

var fs = require('fs');fs.unlinkSync('/tmp/hello');console.log('successfully deleted /tmp/hello');

异步方法不能保证操作顺序,因此下面的例子很容易出错:

fs.rename('/tmp/hello', '/tmp/world', function (err) {  if (err) throw err;  console.log('renamed complete');});fs.stat('/tmp/world', function (err, stats) {  if (err) throw err;  console.log('stats: ' + JSON.stringify(stats));});

该例子出错的原因很可能是因为先执行了fs.stat方法,正确的方法如下:

fs.rename('/tmp/hello', '/tmp/world', function (err) {  if (err) throw err;  fs.stat('/tmp/world', function (err, stats) {    if (err) throw err;    console.log('stats: ' + JSON.stringify(stats));  });});

在繁忙的进程里,强烈建议使用异步方法。同步方法会阻塞整个进程,直到方法完成。

可能会用到相对路径,路径是相对process.cwd()来说的。

大部分fs函数会忽略回调参数,如果忽略,将会用默认函数抛出异常。如果想得到原调用点的堆栈信息,需要设置环境变量NODE_DEBUG:

$ cat script.jsfunction bad() {  require('fs').readFile('/');}bad();$ env NODE_DEBUG=fs node script.jsfs.js:66        throw err;              ^Error: EISDIR, read    at rethrow (fs.js:61:21)    at maybeCallback (fs.js:79:42)    at Object.fs.readFile (fs.js:153:18)    at bad (/path/to/script.js:2:17)    at Object.<anonymous> (/path/to/script.js:5:1)    <etc.>

fs.rename(oldPath, newPath, callback)

异步函数rename(2)。回调函数只有一个参数:可能出现的异常。

fs.renameSync(oldPath, newPath)

同步函数rename(2)。 返回undefined

fs.ftruncate(fd, len, callback)

异步函数ftruncate(2)。 回调函数只有一个参数:可能出现的异常。

fs.ftruncateSync(fd, len)

同步函数ftruncate(2)。 返回undefined

fs.truncate(path, len, callback)

异步函数truncate(2)。 回调函数只有一个参数:可能出现的异常。 文件描述符也可以作为第一个参数,如果这种情况,调用fs.ftruncate()

fs.truncateSync(path, len)

同步函数truncate(2)。 返回undefined

fs.chown(path, uid, gid, callback)

异步函数chown(2)。回调函数只有一个参数:可能出现的异常。

fs.chownSync(path, uid, gid)

同步函数chown(2)。返回undefined

fs.fchown(fd, uid, gid, callback)

异步函数fchown(2)。回调函数只有一个参数:可能出现的异常。

fs.fchownSync(fd, uid, gid)

同步函数 fchown(2)。返回undefined

fs.lchown(path, uid, gid, callback)

异步函数lchown(2)。回调函数只有一个参数:可能出现的异常。

fs.lchownSync(path, uid, gid)

同步函数lchown(2)。返回undefined

fs.chmod(path, mode, callback)

异步函数chmod(2)。回调函数只有一个参数:可能出现的异常。

fs.chmodSync(path, mode)

同步函数chmod(2)。返回 undefined

fs.fchmod(fd, mode, callback)

异步函数fchmod(2)。回调函数只有一个参数:可能出现的异常。

fs.fchmodSync(fd, mode)

同步函数fchmod(2)。返回undefined

fs.lchmod(path, mode, callback)

异步函数 lchmod(2)。回调函数只有一个参数:可能出现的异常。

仅在Mac OS X可用。

fs.lchmodSync(path, mode)

同步函数lchmod(2)。返回undefined

fs.stat(path, callback)

异步函数stat(2)。回调函数有两个参数:(err, stats) ,其中stats是一个fs.Stats对象。 详情请参考fs.Stats。

fs.lstat(path, callback)

异步函数lstat(2)。回调函数有两个参数:(err, stats) ,其中stats是一个fs.Stats对象。lstat()stat()基本相同,区别在于,如果path是链接,读取的是链接本身,而不是它所链接到的文件。

fs.fstat(fd, callback)

异步函数fstat(2)。回调函数有两个参数: (err, stats),其中stats是一个fs.Stats对象。

fs.statSync(path)

同步函数stat(2)。返回fs.Stats实例。

fs.lstatSync(path)

同步函数lstat(2)。返回fs.Stats实例。

fs.fstatSync(fd)

同步函数fstat(2)。返回fs.Stats实例。

fs.link(srcpath, dstpath, callback)

异步函数link(2)。回调函数只有一个参数:可能出现的异常。

fs.linkSync(srcpath, dstpath)

同步函数link(2)。返回undefined

fs.symlink(srcpath, dstpath[, type], callback)

异步函数symlink(2)。回调函数只有一个参数:可能出现的异常。

type可能是'dir','file', 或'junction' (默认'file') ,仅在Windows(不考虑其他系统)有效。注意, Windows junction要求目的地址需要绝对的。当使用'junction'的时候,destination参数将会自动转换为绝对路径。

fs.symlinkSync(srcpath, dstpath[, type])

同步函数symlink(2)。 返回undefined

fs.readlink(path, callback)

异步函数readlink(2)。回调函数有2个参数(err, linkString).

fs.readlinkSync(path)

同步函数readlink(2)。返回符号链接的字符串值。

fs.realpath(path[, cache], callback)

异步函数realpath(2)。回调函数有2个参数(err,resolvedPath)。可以使用process.cwd来解决相对路径问题。

例如:

var cache = {'/etc':'/private/etc'};fs.realpath('/etc/passwd', cache, function (err, resolvedPath) {  if (err) throw err;  console.log(resolvedPath);});

fs.realpathSync(path[, cache])

同步函数realpath(2)。返回解析出的路径。

fs.unlink(path, callback)

异步函数unlink(2)。回调函数只有一个参数:可能出现的异常.

fs.unlinkSync(path)

同步函数unlink(2)。返回undefined

fs.rmdir(path, callback)

异步函数rmdir(2)。回调函数只有一个参数:可能出现的异常.

fs.rmdirSync(path)

同步函数rmdir(2)。返回undefined

fs.mkdir(path[, mode], callback)

异步函数mkdir(2)。回调函数只有一个参数:可能出现的异常. mode默认为0777.

fs.mkdirSync(path[, mode])

同步函数mkdir(2)。返回undefined

fs.readdir(path, callback)

异步函数readdir(3)。读取文件夹的内容。回调有2个参数 (err, files)files是文件夹里除了名字为'.'和'..'之外的所有文件名。

fs.readdirSync(path)

同步函数readdir(3)。返回除了文件名为'.''..'之外的所有文件.

fs.close(fd, callback)

异步函数close(2)。回调函数只有一个参数:可能出现的异常.

fs.closeSync(fd)

同步函数close(2)。返回 undefined

fs.open(path, flags[, mode], callback)

异步函数file open. 参见open(2)。flags是:

  • 'r'- 以只读模式打开;如果文件不存在,抛出异常。

  • 'r+'-以读写模式打开;如果文件不存在,抛出异常。

  • 'rs'- 同步的,以只读模式打开;指令绕过操作系统直接使用本地文件系统缓存。这个功能主要用来打开NFS挂载的文件,因为它能让你跳过可能过时的本地缓存。如果对I/O性能很在乎,就不要使用这个标志位。

    这里不是调用fs.open()变成同步阻塞请求,如果你想要这样,可以调用fs.openSync()

  • 'rs+'- 同步模式下以读写方式打开文件。注意事项参见'rs'.

  • 'w'- 以只写模式打开。文件会被创建 (如果文件不存在) 或者覆盖 (如果存在)。

  • 'wx'- 和'w'类似,如果文件存储操作失败

  • 'w+'- 以可读写方式打开。文件会被创建 (如果文件不存在) 或者覆盖 (如果存在)

  • 'wx+'- 和'w+'类似,如果文件存储操作失败。

  • 'a'- 以附加的形式打开。如果文件不存在则创建一个。

  • 'ax'- 和'a'类似,如果文件存储操作失败。

  • 'a+'- 以只读和附加的形式打开文件.若文件不存在,则会建立该文件

  • 'ax+'- 和'a+'类似,如果文件存储操作失败.

如果文件存在,参数mode设置文件模式 (permission和sticky bits)。 默认是0666,可读写。

回调有2个参数(err, fd).

排除标记'x'(对应open(2)的O_EXCL标记) 保证path是新创建的。在POSIX系统里,即使文件不存在,也会被认定为文件存在。排除标记不能确定在网络文件系统中是否有效。

Linux系统里,无法对以追加模式打开的文件进行指定位置写。系统核心忽略了位置参数,每次把数据写到文件的最后。

fs.openSync(path, flags[, mode])

fs.open()的同步版本. 返回整数形式的文件描述符。.

fs.utimes(path, atime, mtime, callback)

改变指定路径文件的时间戳。

fs.utimesSync(path, atime, mtime)

fs.utimes()的同步版本。返回undefined

fs.futimes(fd, atime, mtime, callback)

改变传入的文件描述符指向文件的时间戳。

fs.futimesSync(fd, atime, mtime)

fs.futimes()的同步版本。返回undefined

fs.fsync(fd, callback)

异步函数fsync(2)。回调函数只有一个参数:可能出现的异常.

fs.fsyncSync(fd)

同步fsync(2)。返回undefined

fs.write(fd, buffer, offset, length[, position], callback)

buffer写到fd指定的文件里。

参数offsetlength确定写哪个部分的缓存。

参数position是要写入的文件位置。如果typeof position !== 'number',将会在当前位置写入。参见pwrite(2)。

回调函数有三个参数(err, written, buffer)written指定buffer的多少字节用来写。

注意,如果fs.write 的回调还没执行,就多次调用fs.write,这样很不安全。因此,推荐使用fs.createWriteStream

Linux系统里,无法对以追加模式打开的文件进行指定位置写。系统核心忽略了位置参数,每次把数据写到文件的最后。

fs.write(fd, data[, position[, encoding]], callback)

buffer写到fd指定的文件里。如果data不是buffer,那么它就会被强制转换为字符串。

参数position是要写入的文件位置。如果typeof position !== 'number',将会在当前位置写入。参见pwrite(2)。

参数encoding :字符串的编码方式.

回调函数有三个参数(err, written, buffer)written指定buffer的多少字节用来写。注意写入的字节(bytes)和字符(string characters)不同。参见Buffer.byteLength

和写入buffer不同,必须写入整个字符串,不能截取字符串。这是因为返回的字节的位移跟字符串的位移是不一样的。

注意,如果fs.write的回调还没执行,就多次调用fs.write,这样很不安全。因此,推荐使用fs.createWriteStream

Linux系统里,无法对以追加模式打开的文件进行指定位置写。系统核心忽略了位置参数,每次把数据写到文件的最后。

fs.writeSync(fd, buffer, offset, length[, position])

fs.writeSync(fd, data[, position[, encoding]])

fs.write()的同步版本. 返回要写的bytes数.

fs.read(fd, buffer, offset, length, position, callback)

读取fd指定文件的数据。

buffer是缓冲区,数据将会写入到这里.

offset写入的偏移量

length需要读的文件长度

position读取的文件起始位置,如果positionnull, 将会从当前位置读。

回调函数有3个参数,(err, bytesRead, buffer).

fs.readSync(fd, buffer, offset, length, position)

fs.read的同步版本。返回bytesRead的数量.

fs.readFile(filename[, options], callback)

  • filename {String}
  • options {Object}
    • encoding {String | Null} 默认 = null
    • flag {String} 默认 = 'r'
  • callback {Function}

异步读取整个文件的内容。例如:

fs.readFile('/etc/passwd', function (err, data) {  if (err) throw err;  console.log(data);});

回调函数有2个参数(err, data),参数data是文件的内容。如果没有指定参数encoding,返回原生buffer

fs.readFileSync(filename[, options])

fs.readFile的同步版本. 返回整个文件的内容.

如果没有指定参数encoding,返回buffer。

fs.writeFile(filename, data[, options], callback)

  • filename {String}
  • data {String | Buffer}
  • options {Object}
    • encoding {String | Null} 默认 = 'utf8'
    • mode {Number} 默认 = 438 (aka 0666 in Octal)
    • flag {String} 默认 = 'w'
  • callback {Function}

异步写文件,如果文件已经存在则替换。data可以是缓存或者字符串。

如果参数data是buffer,会忽略参数encoding。默认值是'utf8'

列如:

fs.writeFile('message.txt', 'Hello Node', function (err) {  if (err) throw err;  console.log('It's saved!');});

fs.writeFileSync(filename, data[, options])

fs.writeFile的同步版本。返回undefined

fs.appendFile(filename, data[, options], callback)

  • filename {String}
  • data {String | Buffer}
  • options {Object}
    • encoding {String | Null} 默认 = 'utf8'
    • mode {Number} 默认 = 438 (aka 0666 in Octal)
    • flag {String} 默认 = 'a'
  • callback {Function}

异步的给文件添加数据,如果文件不存在,就创建一个。data可以是缓存或者字符串。

例如:

fs.appendFile('message.txt', 'data to append', function (err) {  if (err) throw err;  console.log('The "data to append" was appended to file!');});

fs.appendFileSync(filename, data[, options])

fs.appendFile的同步版本。返回undefined

fs.watchFile(filename[, options], listener)

稳定性: 2 - 不稳定。  尽可能的用 fs.watch 来替换。

监视filename文件的变化。每当文件被访问的时候都会调用listener

第二个参数可选。如果有,它必须包含两个boolean参数(persistentinterval)的对象。persistent指定文件被监视时进程是否继续运行。interval指定了查询文件的间隔,以毫秒为单位。缺省值为{ persistent: true, interval: 5007 }。

listener有两个参数,第一个为文件现在的状态,第二个为文件的前一个状态:

fs.watchFile('message.text', function (curr, prev) {  console.log('the current mtime is: ' + curr.mtime);  console.log('the previous mtime was: ' + prev.mtime);});

listener中的文件状态对象类型为fs.Stat。

如果想修改文件时被通知,而不是访问的时候就通知,可以比较curr.mtimeprev.mtime

fs.unwatchFile(filename[, listener])

稳定性: 2 - 不稳定. 尽可能的用 fs.watch 来替换。

停止监视filename文件的变化。如果指定了listener,那只会移除这个listener。否则,移除所有的listener,并会停止监视filename

调用fs.unwatchFile()停止监视一个没被监视的文件,不会触发错误,而会发生一个no-op。

fs.watch(filename[, options][, listener])

稳定性: 2 - 不稳定.

观察filename指定的文件或文件夹的改变。返回对象是 fs.FSWatcher

第二个参数可选。如果有,它必须是包含两个boolean参数(persistentrecursive)的对象。persistent指定文件被监视时进程是否继续运行。 recursive表明是监视所有的子文件夹还是当前文件夹,这个参数只有监视对象是文件夹时才有效,而且仅在支持的系统里有效(参见下面注意事项)。

默认值{ persistent: true, recursive: false }.

回调函数有2个参数(event, filename)eventrenamechangefilename是触发事件的文件名。

注意事项

fs.watchAPI 不是100%的跨平台兼容,可能在某些情况下不可用。

recursive参数仅在OS X上可用。仅FSEvents支持这个类型文件的监视,所以未来也不太可能有新的平台加入。

可用性

这些特性依赖于底层系统提供文件系统变动的通知。

  • Linux系统,使用inotify.
  • BSD系统,使用kqueue.
  • OS X,文件使用kqueue,文件夹使用FSEvents.
  • SunOS 系统(包括Solaris和SmartOS),使用event ports.
  • Windows系统,依赖与ReadDirectoryChangesW.

如果底层系统函数不可用,那么fs.watch就无法工作。例如,监视网络文件系统(NFS、 SMB等)经常不能用。你仍然可以用fs.watchFile查询,但是会比较慢,且不可靠。

文件名参数

回调函数中提供文件名参数,不是每个平台都能用(Linux和Windows就不行)。即使在可用的平台,也不能保证都能提供。所以不要假设回调函数中filename参数有效,要在代码里添加一些为空的逻辑判断。

fs.watch('somedir', function (event, filename) {  console.log('event is: ' + event);  if (filename) {    console.log('filename provided: ' + filename);  } else {    console.log('filename not provided');  }});

fs.exists(path, callback)

判断文件是否存在,回调函数参数是bool值。例如:

fs.exists('/etc/passwd', function (exists) {  util.debug(exists ? "it's there" : "no passwd!");});

fs.exists()是老版本的函数,因此在代码里不要用。

另外,打开文件前判断是否存在有漏洞,在fs.exists()fs.open()调用中间,另外一个进程有可能已经移除了文件。最好用fs.open()来打开文件,根据回调函数来判断是否有错误。

fs.exists()未来会被移除。

fs.existsSync(path)

fs.exists()的同步版本. 如果文件存在返回true, 否则返回false

fs.existsSync()未来会被移除。

fs.access(path[, mode], callback)

测试由参数path指向的文件的用户权限。可选参数mode为整数,它表示需要检查的权限。下面列出了所有值。mode可以是单个值,或者可以通过或运算,掩码运算实现多个权限检查。

  • fs.F_OK- 文件是对于进程可见,可以用来检查文件是否存在。参数mode的默认值。
  • fs.R_OK- 文件对于进程是否可读。
  • fs.W_OK- 文件对于进程是否可写。
  • fs.X_OK- 文件对于进程是否可执行。(Windows系统不可用,执行效果等同fs.F_OK

第三个参数是回调函数。如果检查失败,回调函数的参数就是响应的错误。下面的例子检查文件/etc/passwd是否能被当前的进程读写。

fs.access('/etc/passwd', fs.R_OK | fs.W_OK, function(err) {  util.debug(err ? 'no access!' : 'can read/write');});

fs.accessSync(path[, mode])

fs.access的同步版本. 如果发生错误抛出异常,否则不做任何事情。

类: fs.Stats

fs.stat(), fs.lstat()fs.fstat()以及同步版本的返回对象。

  • stats.isFile()
  • stats.isDirectory()
  • stats.isBlockDevice()
  • stats.isCharacterDevice()
  • stats.isSymbolicLink() (only valid with fs.lstat())
  • stats.isFIFO()
  • stats.isSocket()

对普通文件使用util.inspect(stats),返回的字符串和下面类似:

{ dev: 2114,  ino: 48064969,  mode: 33188,  nlink: 1,  uid: 85,  gid: 100,  rdev: 0,  size: 527,  blksize: 4096,  blocks: 8,  atime: Mon, 10 Oct 2011 23:24:11 GMT,  mtime: Mon, 10 Oct 2011 23:24:11 GMT,  ctime: Mon, 10 Oct 2011 23:24:11 GMT,  birthtime: Mon, 10 Oct 2011 23:24:11 GMT }

atime, mtime, birthtime, 和ctime都是Date的实例,需要使用合适的方法来比较这些值。通常使用getTime()来获取时间戳(毫秒,从 1 January 1970 00:00:00 UTC开始算),这个整数基本能满足任何比较条件。也有一些其他方法来显示额外信息。更多参见MDN JavaScript Reference

Stat Time Values

状态对象(stat object)有以下语义:

  • atime访问时间 - 文件的最后访问时间. mknod(2), utimes(2), 和 read(2)等系统调用可以改变.
  • mtime修改时间 - 文件的最后修改时间. mknod(2), utimes(2), 和 write(2)等系统调用可以改变.
  • ctime改变时间 - 文件状态(inode)的最后修改时间. chmod(2), chown(2),link(2), mknod(2), rename(2), unlink(2), utimes(2), read(2), 和write(2)等系统调用可以改变.
  • birthtime"Birth Time" - 文件创建时间,文件创建时生成。 在一些不提供文件birthtime的文件系统中, 这个字段会使用ctime或1970-01-01T00:00Z (ie, unix epoch timestamp 0)来填充。在Darwin和其他FreeBSD系统变体中,也将atime显式地设置成比它现在的birthtime更早的一个时间值,这个过程使用了 utimes(2)系统调用。

在Node v0.12版本之前,Windows系统里ctime有birthtime值. 注意在v.0.12版本中, ctime不再是"creation time", 而且在Unix系统中,他一直都不是。

fs.createReadStream(path[, options])

返回可读流对象 (见Readable Stream)。

options默认值如下:

{ flags: 'r',  encoding: null,  fd: null,  mode: 0666,  autoClose: true}

参数options提供startend位置来读取文件的特定范围内容,而不是整个文件。startend都在文件范围里,并从0开始,encoding'utf8', 'ascii''base64'

如果给了fd值,ReadStream将会忽略path参数,而使用文件描述,这样不会触发任何open事件。

如果autoClose为false,即使发生错误文件也不会关闭,需要你来负责关闭,避免文件描述符泄露。如果autoClose是true(默认值),遇到errorend,文件描述符将会自动关闭。

例如,从100个字节的文件里,读取最少10个字节:

fs.createReadStream('sample.txt', {start: 90, end: 99});

Class: fs.ReadStream

ReadStreamReadable Stream

Event: 'open'

  • fd{Integer} ReadStream 所使用的文件描述符。

当创建文件的ReadStream时触发。

fs.createWriteStream(path[, options])

返回一个新的写对象 (参见 Writable Stream)。

options是一个对象,默认值:

{ flags: 'w',  encoding: null,  fd: null,  mode: 0666 }

options也可以包含一个start选项,在指定文件中写入数据开始位置。 修改而不替换文件需要flags的模式指定为r+而不是默值的w。

和之前的ReadStream类似,如果fd不为空,WriteStream将会忽略path参数,转而使用文件描述,这样不会触发任何open事件。

类: fs.WriteStream

WriteStreamWritable Stream

Event: 'open'

  • fd{Integer} WriteStream所用的文件描述符

打开WriteStream file时触发。

file.bytesWritten

目前写入的字节数,不含等待写入的数据。

Class: fs.FSWatcher

fs.watch()返回的对象就是这个类.

watcher.close()

停止观察fs.FSWatcher对象中的更改。

Event: 'change'

  • event{String} fs改变的类型
  • filename{String} 改变的文件名 (if relevant/available)

当监听的文件或文件夹改变的时候触发,参见fs.watch

Event: 'error'

  • error{Error object}

错误发生时触发。

相关文章

JavaScript 错误 - throw、try 和 catch


本节为你介绍Node.js Query Strings。

稳定性: 3 - 稳定

该Node.js模块提供了一些处理query strings的工具,你可以通过以下方式访问它:

const querystring = require('querystring');

Node.js Query Strings包含的方法如下:

querystring.stringify(obj[, sep][, eq][, options])

该方法可以将一个对象序列化化为一个query string 。

可以选择重写默认的分隔符('&') 和分配符 ('=')。

Options对象可能包含encodeURIComponent属性 (默认:querystring.escape),如果需要,它可以用non-utf8编码字符串。

例子:

querystring.stringify({ foo: 'bar', baz: ['qux', 'quux'], corge: '' })// returns'foo=bar&baz=qux&baz=quux&corge='querystring.stringify({foo: 'bar', baz: 'qux'}, ';', ':')// returns'foo:bar;baz:qux'// Suppose gbkEncodeURIComponent function already exists,// it can encode string with `gbk` encodingquerystring.stringify({ w: '中文', foo: 'bar' }, null, null,  { encodeURIComponent: gbkEncodeURIComponent })// returns'w=%D6%D0%CE%C4&foo=bar'

querystring.parse(str[, sep][, eq][, options])

该方法可以将query string反序列化为对象。

你可以选择重写默认的分隔符('&') 和分配符 ('=')。

Options对象可能包含maxKeys属性(默认:1000),用来限制处理过的健值(keys)。设置为0的话,可以去掉键值的数量限制。

Options 对象可能包含decodeURIComponent属性(默认:querystring.unescape),如果需要,可以用来解码non-utf8编码的字符串。

例子:

querystring.parse('foo=bar&baz=qux&baz=quux&corge')// returns{ foo: 'bar', baz: ['qux', 'quux'], corge: '' }// Suppose gbkDecodeURIComponent function already exists,// it can decode `gbk` encoding stringquerystring.parse('w=%D6%D0%CE%C4&foo=bar', null, null,  { decodeURIComponent: gbkDecodeURIComponent })// returns{ w: '中文', foo: 'bar' }

querystring.escape

escape函数供querystring.stringify使用,必要时,可以重写。

querystring.unescape

unescape函数供querystring.parse使用。必要时,可以重写。

首先会尝试用decodeURIComponent,如果失败,会回退,不会抛出格式不正确的URLs。


Node.js Addons(插件)是动态链接的共享对象。他提供了C/C++类库能力。这些API比较复杂,他包以下几个类库:

  • V8 JavaScript, C++类库。用来和JavaScript交互,比如创建对象,调用函数等等。在v8.h头文件中 (目录地址deps/v8/include/v8.h),线上地址online

  • libuv,C事件循环库。等待文件描述符变为可读,等待定时器,等待信号时,会和libuv打交道。或者说,如果你需要和I/O打交道,就会用到libuv。

  • 内部Node类库。其中最重要的类node::ObjectWrap,你会经常派生自它。

  • 其他的参见deps/

Node已经将所有的依赖编译成可以执行文件,所以你不必担心这些类库的链接问题。

以下所有例子可以在download下载,也许你可以从中找一个作为你的扩展插件。

Hello world

现在我们来写一个C++插件的小例子,它的效果和以下JS代码一致:

module.exports.hello = function() { return 'world'; };

通过以下代码来创建hello.cc文件:

// hello.cc#include <node.h>using namespace v8;void Method(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world"));}void init(Handle<Object> exports) {  NODE_SET_METHOD(exports, "hello", Method);}NODE_MODULE(addon, init)

注意:所有的Node插件必须输出一个初始化函数:

void Initialize (Handle<Object> exports);NODE_MODULE(module_name, Initialize)

NODE_MODULE之后的代码没有分号,因为它不是一个函数 (参见node.h)。

module_name必须和二进制文件名字一致 (后缀是.node)。

源文件会编译成addon.node二进制插件。为此我们创建了一个很像JSON的binding.gyp文件,它包含配置信息,这个文件用node-gyp编译。

{  "targets": [    {      "target_name": "addon",      "sources": [ "hello.cc" ]    }  ]}

下一步创建一个node-gyp configure工程,在平台上生成这些文件。

创建后,在build/文件夹里拥有一个Makefile (Unix系统) 文件或者vcxproj文件(Windows 系统)。接着调用node-gyp build命令编译,生成.node文件。这些文件位于build/Release/目录里。

现在,你能在Node工程中使用这些二进制扩展插件,在hello.js中声明require之前编译的hello.node:

// hello.jsvar addon = require('./build/Release/addon');console.log(addon.hello()); // 'world'

更多的信息请参考https://github.com/arturadib/node-qt

插件模式

下面是一些addon插件的模式,帮助你开始编码。v8 reference文档里包含v8的各种接口,Embedder's Guide这个文档包含各种说明,比如handles, scopes, function templates等等。

在使用这些例子前,你需要先用node-gyp编译。创建binding.gyp 文件:

{  "targets": [    {      "target_name": "addon",      "sources": [ "addon.cc" ]    }  ]}

将文件名加入到sources数组里就可以使用多个.cc文件,例如 :

"sources": ["addon.cc", "myexample.cc"]

准备好binding.gyp文件后, 你就能配置并编译插件:

$ node-gyp configure build

函数参数

从以下模式中解释了如何从JavaScript函数中读取参数,并返回结果。仅需要一个addon.cc文件:

// addon.cc#include <node.h>using namespace v8;void Add(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  if (args.Length() < 2) {    isolate->ThrowException(Exception::TypeError(        String::NewFromUtf8(isolate, "Wrong number of arguments")));    return;  }  if (!args[0]->IsNumber() || !args[1]->IsNumber()) {    isolate->ThrowException(Exception::TypeError(        String::NewFromUtf8(isolate, "Wrong arguments")));    return;  }  double value = args[0]->NumberValue() + args[1]->NumberValue();  Local<Number> num = Number::New(isolate, value);  args.GetReturnValue().Set(num);}void Init(Handle<Object> exports) {  NODE_SET_METHOD(exports, "add", Add);}NODE_MODULE(addon, Init)

可以用以下的JavaScript代码片段测试:

// test.jsvar addon = require('./build/Release/addon');console.log( 'This should be eight:', addon.add(3,5) );

回调Callbacks

你也能传JavaScript函数给C++函数,并执行它。在addon.cc中:

// addon.cc#include <node.h>using namespace v8;void RunCallback(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  Local<Function> cb = Local<Function>::Cast(args[0]);  const unsigned argc = 1;  Local<Value> argv[argc] = { String::NewFromUtf8(isolate, "hello world") };  cb->Call(isolate->GetCurrentContext()->Global(), argc, argv);}void Init(Handle<Object> exports, Handle<Object> module) {  NODE_SET_METHOD(module, "exports", RunCallback);}NODE_MODULE(addon, Init)

注意,这个例子中使用了Init()里的2个参数,module对象是第二个参数。它允许addon使用一个函数完全重写exports

可以用以下的代码来测试:

// test.jsvar addon = require('./build/Release/addon');addon(function(msg){  console.log(msg); // 'hello world'});

对象工厂

addon.cc模式里,你能用C++函数创建并返回一个新的对象,这个对象所包含的msg属性是由createObject()函数传入:

// addon.cc#include <node.h>using namespace v8;void CreateObject(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  Local<Object> obj = Object::New(isolate);  obj->Set(String::NewFromUtf8(isolate, "msg"), args[0]->ToString());  args.GetReturnValue().Set(obj);}void Init(Handle<Object> exports, Handle<Object> module) {  NODE_SET_METHOD(module, "exports", CreateObject);}NODE_MODULE(addon, Init)

使用JavaScript测试:

// test.jsvar addon = require('./build/Release/addon');var obj1 = addon('hello');var obj2 = addon('world');console.log(obj1.msg+' '+obj2.msg); // 'hello world'

工厂模式

这个模式里展示了如何创建并返回一个JavaScript函数,它是由C++函数包装的 :

// addon.cc#include <node.h>using namespace v8;void MyFunction(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  args.GetReturnValue().Set(String::NewFromUtf8(isolate, "hello world"));}void CreateFunction(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, MyFunction);  Local<Function> fn = tpl->GetFunction();  // omit this to make it anonymous  fn->SetName(String::NewFromUtf8(isolate, "theFunction"));  args.GetReturnValue().Set(fn);}void Init(Handle<Object> exports, Handle<Object> module) {  NODE_SET_METHOD(module, "exports", CreateFunction);}NODE_MODULE(addon, Init)

测试:

// test.jsvar addon = require('./build/Release/addon');var fn = addon();console.log(fn()); // 'hello world'

包装C++对象

以下会创建一个C++对象的包装MyObject,这样他就能在JavaScript中用new实例化。首先在addon.cc中准备主要模块:

// addon.cc#include <node.h>#include "myobject.h"using namespace v8;void InitAll(Handle<Object> exports) {  MyObject::Init(exports);}NODE_MODULE(addon, InitAll)

接着在myobject.h创建包装,它继承自node::ObjectWrap:

// myobject.h#ifndef MYOBJECT_H#define MYOBJECT_H#include <node.h>#include <node_object_wrap.h>class MyObject : public node::ObjectWrap { public:  static void Init(v8::Handle<v8::Object> exports); private:  explicit MyObject(double value = 0);  ~MyObject();  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);  static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args);  static v8::Persistent<v8::Function> constructor;  double value_;};#endif

myobject.cc中实现各种暴露的方法,通过给构造函数添加prototype属性来暴露plusOne方法:

// myobject.cc#include "myobject.h"using namespace v8;Persistent<Function> MyObject::constructor;MyObject::MyObject(double value) : value_(value) {}MyObject::~MyObject() {}void MyObject::Init(Handle<Object> exports) {  Isolate* isolate = Isolate::GetCurrent();  // Prepare constructor template  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);  tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject"));  tpl->InstanceTemplate()->SetInternalFieldCount(1);  // Prototype  NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);  constructor.Reset(isolate, tpl->GetFunction());  exports->Set(String::NewFromUtf8(isolate, "MyObject"),               tpl->GetFunction());}void MyObject::New(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  if (args.IsConstructCall()) {    // Invoked as constructor: `new MyObject(...)`    double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();    MyObject* obj = new MyObject(value);    obj->Wrap(args.This());    args.GetReturnValue().Set(args.This());  } else {    // Invoked as plain function `MyObject(...)`, turn into construct call.    const int argc = 1;    Local<Value> argv[argc] = { args[0] };    Local<Function> cons = Local<Function>::New(isolate, constructor);    args.GetReturnValue().Set(cons->NewInstance(argc, argv));  }}void MyObject::PlusOne(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder());  obj->value_ += 1;  args.GetReturnValue().Set(Number::New(isolate, obj->value_));}

测试:

// test.jsvar addon = require('./build/Release/addon');var obj = new addon.MyObject(10);console.log( obj.plusOne() ); // 11console.log( obj.plusOne() ); // 12console.log( obj.plusOne() ); // 13

包装对象工厂

当你想创建本地对象,又不想在JavaScript中严格的使用new初始化的时候,以下方法非常实用:

var obj = addon.createObject();// instead of:// var obj = new addon.Object();

addon.cc中注册createObject方法:

// addon.cc#include <node.h>#include "myobject.h"using namespace v8;void CreateObject(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  MyObject::NewInstance(args);}void InitAll(Handle<Object> exports, Handle<Object> module) {  MyObject::Init();  NODE_SET_METHOD(module, "exports", CreateObject);}NODE_MODULE(addon, InitAll)

myobject.h中有静态方法NewInstance,他能实例化对象(它就像JavaScript的new):

// myobject.h#ifndef MYOBJECT_H#define MYOBJECT_H#include <node.h>#include <node_object_wrap.h>class MyObject : public node::ObjectWrap { public:  static void Init();  static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args); private:  explicit MyObject(double value = 0);  ~MyObject();  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);  static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args);  static v8::Persistent<v8::Function> constructor;  double value_;};#endif

这个实现方法和myobject.cc类似:

// myobject.cc#include <node.h>#include "myobject.h"using namespace v8;Persistent<Function> MyObject::constructor;MyObject::MyObject(double value) : value_(value) {}MyObject::~MyObject() {}void MyObject::Init() {  Isolate* isolate = Isolate::GetCurrent();  // Prepare constructor template  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);  tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject"));  tpl->InstanceTemplate()->SetInternalFieldCount(1);  // Prototype  NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);  constructor.Reset(isolate, tpl->GetFunction());}void MyObject::New(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  if (args.IsConstructCall()) {    // Invoked as constructor: `new MyObject(...)`    double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();    MyObject* obj = new MyObject(value);    obj->Wrap(args.This());    args.GetReturnValue().Set(args.This());  } else {    // Invoked as plain function `MyObject(...)`, turn into construct call.    const int argc = 1;    Local<Value> argv[argc] = { args[0] };    Local<Function> cons = Local<Function>::New(isolate, constructor);    args.GetReturnValue().Set(cons->NewInstance(argc, argv));  }}void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  const unsigned argc = 1;  Handle<Value> argv[argc] = { args[0] };  Local<Function> cons = Local<Function>::New(isolate, constructor);  Local<Object> instance = cons->NewInstance(argc, argv);  args.GetReturnValue().Set(instance);}void MyObject::PlusOne(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder());  obj->value_ += 1;  args.GetReturnValue().Set(Number::New(isolate, obj->value_));}

测试:

// test.jsvar createObject = require('./build/Release/addon');var obj = createObject(10);console.log( obj.plusOne() ); // 11console.log( obj.plusOne() ); // 12console.log( obj.plusOne() ); // 13var obj2 = createObject(20);console.log( obj2.plusOne() ); // 21console.log( obj2.plusOne() ); // 22console.log( obj2.plusOne() ); // 23

传递包装对象

除了包装并返回C++对象,你可以使用Node的node::ObjectWrap::Unwrap帮助函数来解包。在下面的addon.cc中,我们介绍了一个add()函数,它能获取2个 MyObject对象:

// addon.cc#include <node.h>#include <node_object_wrap.h>#include "myobject.h"using namespace v8;void CreateObject(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  MyObject::NewInstance(args);}void Add(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  MyObject* obj1 = node::ObjectWrap::Unwrap<MyObject>(      args[0]->ToObject());  MyObject* obj2 = node::ObjectWrap::Unwrap<MyObject>(      args[1]->ToObject());  double sum = obj1->value() + obj2->value();  args.GetReturnValue().Set(Number::New(isolate, sum));}void InitAll(Handle<Object> exports) {  MyObject::Init();  NODE_SET_METHOD(exports, "createObject", CreateObject);  NODE_SET_METHOD(exports, "add", Add);}NODE_MODULE(addon, InitAll)

介绍myobject.h里的一个公开方法,它能在解包后使用私有变量:

// myobject.h#ifndef MYOBJECT_H#define MYOBJECT_H#include <node.h>#include <node_object_wrap.h>class MyObject : public node::ObjectWrap { public:  static void Init();  static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);  inline double value() const { return value_; } private:  explicit MyObject(double value = 0);  ~MyObject();  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);  static v8::Persistent<v8::Function> constructor;  double value_;};#endif

myobject.cc的实现方法和之前的类似:

// myobject.cc#include <node.h>#include "myobject.h"using namespace v8;Persistent<Function> MyObject::constructor;MyObject::MyObject(double value) : value_(value) {}MyObject::~MyObject() {}void MyObject::Init() {  Isolate* isolate = Isolate::GetCurrent();  // Prepare constructor template  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);  tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject"));  tpl->InstanceTemplate()->SetInternalFieldCount(1);  constructor.Reset(isolate, tpl->GetFunction());}void MyObject::New(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  if (args.IsConstructCall()) {    // Invoked as constructor: `new MyObject(...)`    double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();    MyObject* obj = new MyObject(value);    obj->Wrap(args.This());    args.GetReturnValue().Set(args.This());  } else {    // Invoked as plain function `MyObject(...)`, turn into construct call.    const int argc = 1;    Local<Value> argv[argc] = { args[0] };    Local<Function> cons = Local<Function>::New(isolate, constructor);    args.GetReturnValue().Set(cons->NewInstance(argc, argv));  }}void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  const unsigned argc = 1;  Handle<Value> argv[argc] = { args[0] };  Local<Function> cons = Local<Function>::New(isolate, constructor);  Local<Object> instance = cons->NewInstance(argc, argv);  args.GetReturnValue().Set(instance);}

测试:

// test.jsvar addon = require('./build/Release/addon');var obj1 = addon.createObject(10);var obj2 = addon.createObject(20);var result = addon.add(obj1, obj2);console.log(result); // 30

相关教程

如果你想了解更多与C++相关的知识,则可以参考本站的《C++教程》


本节介绍Node.js readline(逐行读取)模块,它用于提供一个接口。

稳定性: 2 - 不稳定

通过 require('readline'),你可以使用这个模块。逐行读取(Readline)可以逐行读取流(比如process.stdin)。

访问该模块的方法如下:

const readline = require('readline');

一旦你开启了这个模块,node程序将不会终止,直到你关闭接口。以下的代码展示了如何优雅的退出程序:

var readline = require('readline');var rl = readline.createInterface({  input: process.stdin,  output: process.stdout});rl.question("What do you think of node.js? ", function(answer) {  // TODO: Log the answer in a database  console.log("Thank you for your valuable feedback:", answer);  rl.close();});

readline.createInterface(options)

创建一个逐行读取(Readline)Interface实例。参数"options"对象有以下值:

  • input- 监听的可读流 (必填)。

  • output- 逐行读取(Readline)数据要写入的可写流(可选)。

  • completer- 用于Tab自动补全的可选函数。参见下面的例子。

  • terminal- 如果希望和TTY一样,对待inputoutput流,设置为true。并且由ANSI/VT100转码。默认情况下,检查isTTY是否在output流上实例化。

completer给出当前行的入口,应该返回包含2条记录的数组。

  1. 一个匹配当前输入补全的字符串数组

  2. 用来匹配的子字符串

最终像这样:[[substr1, substr2, ...], originalsubstring].

例子:

function completer(line) {  var completions = '.help .error .exit .quit .q'.split(' ')  var hits = completions.filter(function(c) { return c.indexOf(line) == 0 })  // show all completions if none found  return [hits.length ? hits : completions, line]}

同时,completer可以异步运行,此时接收到2个参数:

function completer(linePartial, callback) {  callback(null, [['123'], linePartial]);}

为了接受用户输入,createInterface通常和process.stdinprocess.stdout一起使用:

var readline = require('readline');var rl = readline.createInterface({  input: process.stdin,  output: process.stdout});

如果你有逐行读取(Readline)实例, 通常会监听"line"事件.

如果这个实例参数terminal = true,而且定义了output.columns属性,那么output流将会最佳兼容性,并且,当columns变化时(当它是TTY时,process.stdout会自动这么做),会在output流上触发"resize"事件。

Class: Interface

代表一个包含输入/输出流的逐行读取(Readline)接口的类。

rl.setPrompt(prompt)

设置提示符,比如当你再命令行里运行node时,可以看到node的提示符>

rl.prompt([preserveCursor])

为用户输入准备好逐行读取(Readline),将当前setPrompt选项方法哦新的行中,让用户有新的地方输入。设置preserveCursortrue,防止当前的游标重置为0

如果暂停,使用createInterface也可以重置input输入流。

调用createInterface时,如果output设置为nullundefined,不会重新写提示符。

rl.question(query, callback)

预先提示query,用户应答后触发callback。给用户显示query后,用户应答被输入后,调用callback

如果暂停,使用createInterface也可以重置input输入流。

调用createInterface时,如果output设置为nullundefined,不会重新写提示符。

例子:

interface.question('What is your favorite food?', function(answer) {  console.log('Oh, so your favorite food is ' + answer);});

rl.pause()

暂停逐行读取(Readline)的input输入流, 如果需要可以重新启动。

注意,这不会立即暂停流。调用pause后还会有很多事件触发,包含line

rl.resume()

恢复 逐行读取(Readline)input输入流.

rl.close()

关闭Interface实例, 放弃控制输入输出流。会触发"close"事件。

rl.write(data[, key])

调用createInterface后,将数据data写到output输出流,除非outputnull,或未定义undefinedkey是一个代表键序列的对象;当终端是一个 TTY 时可用。

暂停input输入流后,这个方法可以恢复。

例子:

rl.write('Delete me!');// Simulate ctrl+u to delete the line written previouslyrl.write(null, {ctrl: true, name: 'u'});

Events

事件: 'line'

function (line) {}

input输入流收到 后触发,通常因为用户敲回车或返回键。这是监听用户输入的好办法。

监听line的例子:

rl.on('line', function (cmd) {  console.log('You just typed: '+cmd);});

事件: 'pause'

function () {}

暂停input输入流后,会触发这个方法。

当输入流未被暂停,但收到SIGCONT也会触发。 (详见SIGTSTPSIGCONT事件)

监听pause的例子:

rl.on('pause', function() {  console.log('Readline paused.');});

事件: 'resume'

function () {}

恢复input输入流后,会触发这个方法。

监听resume的例子:

rl.on('resume', function() {  console.log('Readline resumed.');});

事件: 'close'

function () {}

调用close()方法时会触发。

input输入流收到"end"事件时会触发。一旦触发,可以认为Interface实例结束。例如当input输入流收到^D,被当做EOT

如果没有SIGINT事件监听器,当input 输入流接收到^C(被当做SIGINT),也会触发这个事件。

事件: 'SIGINT'

function () {}

input输入流收到^C时会触发, 被当做SIGINT。如果没有SIGINT 事件监听器,当input 输入流接收到SIGINT(被当做SIGINT),会触发 pause事件。

监听SIGINT的例子:

rl.on('SIGINT', function() {  rl.question('Are you sure you want to exit?', function(answer) {    if (answer.match(/^y(es)?$/i)) rl.pause();  });});

事件: 'SIGTSTP'

function () {}

Windows 里不可用

input输入流收到^Z时会触发,被当做SIGTSTP。如果没有SIGINT事件监听器,当input 输入流接收到SIGTSTP,程序将会切换到后台。

当程序通过fg恢复,将会触发pauseSIGCONT事件。你可以使用两者中任一事件来恢复流。

程切换到后台前,如果暂停了流,pauseSIGCONT事件不会被触发。

监听SIGTSTP的例子:

rl.on('SIGTSTP', function() {  // This will override SIGTSTP and prevent the program from going to the  // background.  console.log('Caught SIGTSTP.');});

事件: 'SIGCONT'

function () {}

Windows里不可用

一旦input流中含有^Z并被切换到后台就会触发。被当做SIGTSTP,然后继续执行fg(1)。程切换到后台前,如果流没被暂停,这个事件可以被触发。

监听SIGCONT的例子:

rl.on('SIGCONT', function() {  // `prompt` will automatically resume the stream  rl.prompt();});

例子: Tiny CLI

以下的例子,展示了如何所有这些方法的命令行接口:

var readline = require('readline'),    rl = readline.createInterface(process.stdin, process.stdout);rl.setPrompt('OHAI> ');rl.prompt();rl.on('line', function(line) {  switch(line.trim()) {    case 'hello':      console.log('world!');      break;    default:      console.log('Say what? I might have heard `' + line.trim() + '`');      break;  }  rl.prompt();}).on('close', function() {  console.log('Have a great day!');  process.exit(0);});

readline.cursorTo(stream, x, y)

在TTY流里,移动光标到指定位置。

readline.moveCursor(stream, dx, dy)

在TTY流里,移动光标到当前位置的相对位置。

readline.clearLine(stream, dir)

清空TTY流里指定方向的行。dir是以下值:

  • -1- 从光标到左边
  • 1- 从光标到右边
  • 0- 整行

readline.clearScreenDown(stream)

清空屏幕上从当前光标位置起的内容。


在Node.js中我们可以直接访问到全局对象。

这些对象在所有模块里都是可用的,有些对象不是在全局作用域而是在模块作用域里,这些情况将在本文的内容中进行介绍。

global

  • {Object} 全局命名空间对象。

在浏览器中,全局作用域就是顶级域。如果在全局域内定义变量var something将会是全局变量。而在Node中,顶级域并不是全局域;在模块里定义变量 var something只是模块内可用。

process

  • {Object}

进程对象。参见process object章节.

console

  • {Object}

用来打印stdout和stderr。参见console章节.

Class: Buffer

  • {Function}

用来处理二进制数据。参见buffer 章节

require()

  • {Function}

引入模块。参见Modules章节。require实际上并非全局的,而是各个本地模块有效。

require.resolve()

使用内部require()机制来查找module位置,但是不加载模块,只是返回解析过的文件名。

require.cache

  • {Object}

引入模块时会缓存到这个对象。通过删除该对象键值,下次调用require将会重载该模块。

require.extensions

稳定性: 0 - 抛弃
  • {Object}

指导require如何处理特定的文件扩展名。

.sjs文件当作.js文件处理:

require.extensions['.sjs'] = require.extensions['.js'];

抛弃 以前这个列表用来加载按需编译的非JavaScript模块到node。实际上,有更好的办法来解决这个问题,比如通过其他node程序来加载模块,或者提前编译成 JavaScript。

由于模块系统已经锁定,该功能可能永远不会去掉。改动它可能会产生bug,所以最好不要动它。

__filename

  • {String}

被执行的代码的文件名是相对路径。对于主程序来说,这和命令行里未必用同一个文件名。模块里的值是模块文件的路径。

列如,运行/Users/mjr里的node example.js

console.log(__filename);// /Users/mjr/example.js

__filename不是全局的,而是模块本地的。

__dirname

  • {String}

执行的script代码所在的文件夹的名字。

列如,运行/Users/mjr里的node example.js

console.log(__dirname);// /Users/mjr

__dirname不是全局的,而是模块本地的。

module

  • {Object}

当前模块的引用。通过require()module.exports定义了哪个模块输出可用。

module不是全局的,而是模块本地的。

更多信息参见module system documentation

exports

module.exports的引用。关于什么时候使用exportsmodule.exports,可以参考module system documentation

module不是全局的,而是模块本地的。

更多信息参见module system documentation

更多信息参见module 章节

setTimeout(cb, ms)

最少在ms毫秒后调回调函数。实际的延迟依赖于外部因素,比如操作系统的粒度和负载。

timeout值有效范围为1-2,147,483,647。如果超过该范围,将会变为1毫秒。通常,定时器不应该超过24.8天。

返回一个代表定时器的句柄值。

clearTimeout(t)

停止一个之前通过setTimeout()创建的定时器。不会再被执行回调。

setInterval(cb, ms)

每隔ms毫秒调用回调函数cb。实际的间隔依赖于外部因素,比如操作系统的粒度和系统负载。通常会大于ms

间隔值的有效范围在1-2,147,483,647。如果超过该范围,将会变为1毫秒。通常,定时器不应该超过24.8天。

返回一个代表该定时器的句柄值。

clearInterval(t)

停止一个之前通过setInterval()创建的定时器。不会再被执行回调。

timer函数是全局变量。参见timers章节。


Node.js是基于单线程模型架构的,它能够拥有高效的CPU利用率,却限制了多个核心CPU的使用,为此,Node.js提供了child_process 模块以通过多线程来实现对多核CPU的使用。

稳定性: 3 - 稳定

Node通过child_process模块提供了popen(3)数据流。

它能在非阻塞的方式中,通过stdin,stdoutstderr传递数据。 

请注意:某些程序使用内部线性缓冲I/O, 它并不妨碍node.js,只是你发送给子进程的数据不会被立即取消。

你可以使用require('child_process').spawn()require('child_process').fork()创建一个子进程。这两种方法有区别,在下文中将进行解释。

开发过程中查看synchronous counterparts效率会更高。

类: ChildProcess

ChildProcess是一个EventEmitter

子进程有三个相关的流child.stdin,child.stdoutchild.stderr。他们可能和会父进程的stdiostreams共享,也可作为独立的对象。

不能直接调用ChildProcess类,使用spawn(),exec(),execFile()fork()方法来创建子进程的实例。

事件: 'error'

  • err {Error Object}错误。

发生于:

  1. 无法创建进程。
  2. 无法杀死进程。
  3. 无论什么原因导致给子进程发送消息失败。

注意:exit事件有可能在错误发生后调用,也可能不调用,所以如果你监听这两个事件来触发函数,记得预防函数会被调用2次。

参考ChildProcess#kill()ChildProcess#send()

事件: 'exit'

  • code {Number} 退出代码, 正常退出时才有效。
  • signal {String} 如果是被父进程杀死,则它为传递给子进程的信号

子进程结束的时候触发这个事件。如果子进程正常终止,则code为最终的退出代码,否则为null。如果是由signal引起的终止,则signal为字符串,否则为 null

注意:子进程的stdio流可能仍为开启模式。

注意,node为'SIGINT''SIGTERM' 建立句柄,所以当信号来临的时候,他们不会终止而是退出。

参靠waitpid(2)

事件: 'close'

  • code{Number} 退出代码, 正常退出时才有效。
  • signal{String} 如果是被父进程杀死,则它为传递给子进程的信号。

子进程里所有stdio流都关闭时触发这个事件。要和'exit'区分开,因为多进程可以共享一个stdio流。

Event: 'disconnect'

父进程或子进程中调用.disconnect()方法后触发这个事件。断开后不会在互发消息,并且.connected属性值为false。

Event: 'message'

  • message{Object} 一个解析过的JSON对象,或者一个原始值。
  • sendHandle{Handle object} 一个Socket或Server对象

通过.send(message, [sendHandle])传递消息。

child.stdin

  • {Stream object}

子进程的stdinWritable Stream(可写流)。如果子进程在等待输入,它就会暂停直到通过调用end()来关闭。

child.stdinchild.stdio[0]的缩写。这两个都指向同一个对象,或者null。

child.stdout

  • {Stream object}

子进程的stdoutReadable Stream(可读流)。

child.stdoutchild.stdio[1]的缩写。 这两个都指向同一个对象,或者null。

child.stderr

  • {Stream object}

子进程的stderrReadable Stream(可写流)。

child.stderrchild.stdio[2]缩写。这两个都指向同一个对象,或者null。

child.stdio

  • {Array}

子进程的管道数组和spawnstdio里设置为'pipe' 的内容次序相对应。

注意,流[0-2]也能分别用ChildProcess.stdin、ChildProcess.stdout和ChildProcess.stderrNote来表示。

在下面的例子里,只有子进程的fd1设置为pipe管道,所以父进程的child.stdio[1]是流(stream),数组里其他值为null

child = child_process.spawn("ls", {    stdio: [      0, // use parents stdin for child      'pipe', // pipe child's stdout to parent      fs.openSync("err.out", "w") // direct child's stderr to a file    ]});assert.equal(child.stdio[0], null);assert.equal(child.stdio[0], child.stdin);assert(child.stdout);assert.equal(child.stdio[1], child.stdout);assert.equal(child.stdio[2], null);assert.equal(child.stdio[2], child.stderr);

child.pid

  • {Integer}

子进程的PID。

例子:

var spawn = require('child_process').spawn,    grep  = spawn('grep', ['ssh']);console.log('Spawned child pid: ' + grep.pid);grep.stdin.end();

child.connected

  • {Boolean} 调用`.disconnect'后设置为false

如果.connected为 false,消息不再可用。

child.kill([signal])

  • signal{String}

发送信号给子进程。如果没有参数,会发送'SIGTERM',参见signal(7)里的可用的信号列表。

var spawn = require('child_process').spawn,    grep  = spawn('grep', ['ssh']);grep.on('close', function (code, signal) {  console.log('child process terminated due to receipt of signal '+signal);});// send SIGHUP to processgrep.kill('SIGHUP');

当信号无法传递的时候会触发'error'事件。给已经终止的进程发送信号不会触发'error'事件,但是可以能引起不可预知的后果: 因为有可能PID (进程ID) 已经重新分配给其他进程,信号就会被发送到新的进程里,无法想象这样会引发什么样的事情。

注意:当函数调用kill信号的时候,它实际并并不会杀死进程,只是发送信号给进程。

参见kill(2)

child.send(message[, sendHandle])

  • message{Object}
  • sendHandle{Handle object}

使用child_process.fork()的时候,你能用child.send(message, [sendHandle])给子进程写数据,子进程通过'message'接收消息。

例如:

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' :

process.on('message', function(m) {  console.log('CHILD got message:', m);});process.send({ foo: 'bar' });

子进程代码里的process对象拥有send()方法,当它通过信道接收到信息时会触发,并返回对象。

注意:父进程和子进程send()是同步的,不要用来发送大块的数据(可以用管道来代替,参见child_process.spawn)。

不过发送{cmd: 'NODE_foo'}消息是特殊情况。所有包含NODE_前缀的消息都不会被触发,因为它们是node的内部的核心消息,它们会在internalMessage事件里触发,尽量避免使用这个特性。

child.send()里的sendHandle属性用来发送TCP服务或socket对象给其他的进程,子进程会用接收到的对象作为message事件的第二个参数。

如果不能发出消息会触发'error'事件,比如子进程已经退出。

例子: 发送 server 对象

以下是例子:

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');});server.listen(1337, function() {  child.send('server', server);});

子进程将会收到这个server对象:

process.on('message', function(m, server) {  if (m === 'server') {    server.on('connection', function (socket) {      socket.end('handled by child');    });  }});

注意,现在父子进程共享了server,某些连接会被父进程处理,某些会被子进程处理。

dgram服务器,工作流程是一样的,监听的是message事件,而不是connection,使用server.bind而不是server.listen。(目前仅支持UNIX平台)

例子: 发送 socket 对象

以下是发送socket对象的例子。他将会创建2个子线程,并且同时处理连接,一个将远程地址74.125.127.100当做VIP发送到一个特殊的子进程,另外一个发送到正常进程。var normal=require('child_process').fork('child.js', ['normal']);var special = require('child_process').fork('child.js', ['special']);

// Open up the server and send sockets to childvar server = require('net').createServer();server.on('connection', function (socket) {  // if this is a VIP  if (socket.remoteAddress === '74.125.127.100') {    special.send('socket', socket);    return;  }  // just the usual dudes  normal.send('socket', socket);});server.listen(1337);

child.js代码如下:

process.on('message', function(m, socket) {  if (m === 'socket') {    socket.end('You were handled as a ' + process.argv[2] + ' person');  }});

注意,当socket发送给子进程后,如果这个socket被销毁,父进程不再跟踪它,相应的.connections属性会变为null。这种情况下,不建议使用 .maxConnections

child.disconnect()

关闭父子进程间的所有IPC通道,能让子进程优雅的退出。调用这个方法后,父子进程里的.connected标志会变为false,之后不能再发送消息。

当进程里没有消息需要处理的时候,会触发'disconnect'事件。

注意,在子进程还有IPC通道的情况下(如fork()),也可以调用process.disconnect()来关闭它。

创建异步处理

这些方法遵从常用的异步处理模式(比如回调,或者返回一个事件处理)。

child_process.spawn(command[, args][, options])

  • command {String} 要运行的命令
  • args {Array} 字符串参数表
  • options {Object}
    • cwd {String} 子进程的工作目录
    • env {Object} 环境
    • stdio {Array|String} 子进程的stdio配置。
    • customFds {Array} Deprecated 作为子进程stdio使用的文件标示符。
    • detached {Boolean} 子进程将会变成一个进程组的领导者。
    • uid {Number} 设置用户进程的ID。(参见setuid(2))
    • gid {Number} 设置进程组的ID。(参见 setgid(2))
  • 返回: {ChildProcess object}

用指定的command发布一个子进程,args是命令行参数。如果忽略,args是空数组。

第三个参数用来指定附加设置,默认值:

{ cwd: undefined,  env: process.env}

创建的子进程里使用cwd指定工作目录,如果没有指定,默认继承自当前的工作目录。

使用env来指定新进程可见的环境变量。默认是process.env

例如,运行ls -lh /usr,获取stdout,stderr和退出代码:

var spawn = require('child_process').spawn,    ls    = spawn('ls', ['-lh', '/usr']);ls.stdout.on('data', function (data) {  console.log('stdout: ' + data);});ls.stderr.on('data', function (data) {  console.log('stderr: ' + data);});ls.on('close', function (code) {  console.log('child process exited with code ' + code);});

例如:通过一个非常精巧的方法执行'ps ax | grep ssh'

var spawn = require('child_process').spawn,    ps    = spawn('ps', ['ax']),    grep  = spawn('grep', ['ssh']);ps.stdout.on('data', function (data) {  grep.stdin.write(data);});ps.stderr.on('data', function (data) {  console.log('ps stderr: ' + data);});ps.on('close', function (code) {  if (code !== 0) {    console.log('ps process exited with code ' + code);  }  grep.stdin.end();});grep.stdout.on('data', function (data) {  console.log('' + data);});grep.stderr.on('data', function (data) {  console.log('grep stderr: ' + data);});grep.on('close', function (code) {  if (code !== 0) {    console.log('grep process exited with code ' + code);  }});

options.stdio

stdio可能是以下几个参数之一:

  • 'pipe'-['pipe', 'pipe', 'pipe'],默认值
  • 'ignore'-['ignore', 'ignore', 'ignore']
  • 'inherit'-[process.stdin, process.stdout, process.stderr][0,1,2]

child_process.spawn()里的'stdio'参数是一个数组,它和子进程的fd相对应,它的值如下:

  1. 'pipe'- 创建在父进程和子进程间的pipe。管道的父进程端以child_process的属性形式暴露给父进程,例如ChildProcess.stdio[fd]。为fds 0 - 2创建的管道也可以通过ChildProcess.stdin,ChildProcess.stdout和ChildProcess.stderr来独立的访问。

  2. 'ipc'- 在父进程和子进程间创建一个IPC通道来传递消息/文件描述符。一个子进程最多有1个IPC stdio文件标识。设置这个选项会激活ChildProcess.send() 方法。如果子进程向此文件标识写入JSON消息,则会触发ChildProcess.on('message') 。如果子进程是Node.js程序,那么IPC通道会激活process.send()和 process.on('message')。

  3. 'ignore'- 在子进程里不要设置这个文件标识,需要注意,Node总会为其spawn的进程打开fd 0-2。如果任何一个被ignored,node将会打开/dev/null并赋给子进程的fd。

  4. Stream对象 - 共享一个tty、file、socket或刷(pipe)可读或可写流给子进程。该流底层(underlying)的文件标识在子进程中被复制给stdio数组索引对应的文件标识(fd)。

  5. 正数 - 这个整数被理解为一个在父进程中打开的文件标识,它和子进程共享,就和共享Stream对象类似。

  6. null,undefined- 使用默认值。 对于stdio fds 0、1 and 2 (换句话说,stdin、stdout或者stderr) ,pipe管道被建立。 对于fd 3及之后,默认是 'ignore'

例如:

var spawn = require('child_process').spawn;// Child will use parent's stdiosspawn('prg', [], { stdio: 'inherit' });// Spawn child sharing only stderrspawn('prg', [], { stdio: ['pipe', 'pipe', process.stderr] });// Open an extra fd=4, to interact with programs present a// startd-style interface.spawn('prg', [], { stdio: ['pipe', null, null, null, 'pipe'] });

options.detached

如果设置了detached选项,子进程将会被作为新进程组的leader,这使得子进程可以在父进程退出后继续运行。

缺省情况下父进程会等detached的子进程退出。要阻止父进程等待一个这样的子进程,调用child.unref()方法,则父进程的事件循环引用计数中将不会包含这个子进程。

detaching一个长期运行的进程,并重新将输出指向文件:

 var fs = require('fs'),     spawn = require('child_process').spawn,     out = fs.openSync('./out.log', 'a'),     err = fs.openSync('./out.log', 'a'); var child = spawn('prg', [], {   detached: true,   stdio: [ 'ignore', out, err ] }); child.unref();

使用detached选项来启动一个长时间运行的进程时,进程不会在后台保持运行,除非他提供了一个不连接到父进程的stdio。如果继承了父进程的stdio,则子进程会继续控制终端。

options.customFds

已废弃,customFds允许指定特定文件描述符作为子进程的stdio。该API无法移植到所有平台,因此被废弃。使用customFds可以将新进程的 [stdin, stdout,stderr] 钩到已有流上;-1表示创建新流。自己承担使用风险。

参见:child_process.exec()child_process.fork()

child_process.exec(command[, options], callback)

  • command {String} 要执行的命令,空格分割
  • options {Object}
    • cwd {String} 子进程的当前工作目录
    • env {Object} 环境变量
    • encoding {String} (默认: 'utf8')
    • shell {String} 运行命令的shell(默认为: '/bin/sh' UNIX, 'cmd.exe' Windows, 该shell必须接收UNIX上的-c开关 ,或者Windows上的/s /c开关。Windows上,命令解析必须兼容cmd.exe。)
    • timeout {Number} (默认: 0)
    • maxBuffer {Number} (默认: 200*1024)
    • killSignal {String} (默认: 'SIGTERM')
    • uid {Number} 设置进程里的用户标识。 (见 setuid(2)。)
    • gid {Number} 设置进程里的群组标识。(见 setgid(2)。)
  • callback {Function} 进程终止的时候调用
    • error {Error}
    • stdout {Buffer}
    • stderr {Buffer}
  • 返回: ChildProcess对象

在shell里执行命令,并缓冲输出。

var exec = require('child_process').exec,    child;child = exec('cat *.js bad_file | wc -l',  function (error, stdout, stderr) {    console.log('stdout: ' + stdout);    console.log('stderr: ' + stderr);    if (error !== null) {      console.log('exec error: ' + error);    }});

回调参数是(error, stdout, stderr)。如果成功,则,error值为null。 如果失败,则error变为Error的实例,error.code等于子进程退出码,并且 error.signal会被设置为结束进程的信号名。

第二个参数可以设置一些选项。默认如下:

{ encoding: 'utf8',  timeout: 0,  maxBuffer: 200*1024,  killSignal: 'SIGTERM',  cwd: null,  env: null }

如果timeout大于0,子进程运行时间超过timeout时会被终止。killSignal(默认: 'SIGTERM')能杀死子进程。maxBuffer设定了stdout或stderr的最大数据量,如果子进程的数量量超过了,将会被杀死。

(file[, args][, options][, callback])

  • file {String} 要运行的程序的文件名
  • args {Array} 参数列表
  • options {Object}
    • cwd {String} 子进程的工作目录
    • env {Object} 环境
    • encoding {String} (默认: 'utf8')
    • timeout {Number} (默认: 0)
    • maxBuffer {Number} (默认: 200*1024)
    • killSignal {String} (默认: 'SIGTERM')
    • uid {Number} 设置进程里的用户标识。 (参见setuid(2)。)
    • gid {Number} 设置进程里的群组标识。(参见setgid(2)。)
  • callback {Function} 进程终止的时候调用
    • error {Error}
    • stdout {Buffer}
    • stderr {Buffer}
  • 返回: ChildProcess对象

child_process.exec()类似,不同之处在于这是执行一个指定的文件,因此它比child_process.exec精简些,参数相同。

child_process.fork(modulePath[, args][, options])

  • modulePath {String} 子进程里运行的模块
  • args {Array} 参数列表
  • options {Object}
    • cwd {String} 子进程的工作目录
    • env {Object} 环境
    • execPath {String} 执行文件路径
    • execArgv {Array} 执行参数(默认: process.execArgv)
    • silent {Boolean} 如果是 true ,子进程将会用父进程的 stdin, stdout, and stderr ,否则,将会继承自父进程, 更多细节,参见 spawn()stdio 参数里的 "pipe" 和 "inherit" 选项(默认 false)
    • uid {Number} 设置进程里的用户标识。 (见 setuid(2)。)
    • gid {Number} 设置进程里的群组标识。 (见 setgid(2)。)
  • 返回: ChildProcess对象

这是spawn()的特殊例子,用于派生Node进程。除了拥有子进程的所有方法,它的返回对象还拥有内置通讯通道。参见child.send(message, [sendHandle])

这些Nodes是全新的V8实例化,假设每个Node最少需要30ms的启动时间,10mb的存储空间,可想而知,创建几千个Node是不太现实的。

options对象中的execPath属性可以用于执行文件(非当前node )创建子进程。这需要小心使用,缺省情况下fd表示子进程的NODE_CHANNEL_FD环境变量。该fa的输入和输出是以行分割的JSON对象。

创建同步进程

以下这些方法是同步的,意味着他会阻塞事件循环,并暂停执行代码,直到spawned的进程退出。

同步方法简化了任务进程,比如大为简化在应用初始化加载/处理过程。

child_process.spawnSync(command[, args][, options])

  • command {String} 要执行的命令
  • args {Array} 参数列表
  • options {Object}
    • cwd {String} 子进程的当前工作目录
    • input {String|Buffer} 传递给spawned进程的值,这个值将会重写stdio[0]
    • stdio {Array} 子进程的stdio配置。
    • env {Object} 环境变量
    • uid {Number} 设置用户进程的ID。 (参见setuid(2)。)
    • gid {Number} 设置进程组的ID。 (参见setgid(2)。)
    • timeout {Number} 子进程运行最大毫秒数。 (默认: undefined)
    • killSignal {String} 用来终止子进程的信号。 (默认: 'SIGTERM')
    • maxBuffer {Number}
    • encoding {String} stdio输入和输出的编码方式。 (默认: 'buffer')
  • 返回: {Object}
    • pid {Number} 子进程的pid
    • output {Array} stdio输出的结果数组
    • stdout {Buffer|String} output[1]的内容
    • stderr {Buffer|String} output[2]的内容
    • status {Number} 子进程的退出代码
    • signal {String} 用来杀死子进程的信号
    • error {Error} 子进程错误或超时的错误代码

spawnSync直到子进程关闭才会返回。超时或者收到killSignal信号,也不会返回,直到进程完全退出。进程处理完SIGTERM信号后并不会结束,直到子进程完全退出。

child_process.execFileSync(command[, args][, options])

  • command {String} 要执行的命令
  • args {Array} 参数列表
  • options {Object}
    • cwd {String} 子进程的当前工作目录
    • input {String|Buffer}传递给spawned进程的值,这个值将会重写 stdio[0]
    • stdio {Array}子进程的stdio配置。 (默认: 'pipe')
      • stderr 默认情况下会输出给父进程的' stderr除非指定了 stdio
    • env {Object} 环境变量
    • uid {Number} 设置用户进程的ID。 (参见setuid(2)。)
    • gid {Number} 设置进程组的ID。 (参见setgid(2)。)
    • timeout {Number} 进程运行最大毫秒数。 (默认: undefined)
    • killSignal {String} 用来终止子进程的信号。 (默认: 'SIGTERM')
    • maxBuffer {Number}
    • encoding {String} stdio输入和输出的编码方式。 (默认: 'buffer')
  • 返回: {Buffer|String} 来自命令的stdout

直到子进程完全退出,execFileSync才会返回。超时或者收到killSignal信号,也不会返回,直到进程完全退出。进程处理完SIGTERM信号后并不会结束,直到子进程完全退出。

如果进程超时,或者非正常退出,这个方法将会抛出异常。Error会包含整个child_process.spawnSync结果。

child_process.execSync(command[, options])

  • command {String} 要执行的命令
  • options {Object}
    • cwd {String} 子进程的当前工作目录
    • input {String|Buffer} 传递给spawned进程的值,这个值将会重写stdio[0]
    • stdio {Array} 子进程的stdio配置。 (默认: 'pipe')
      • stderr 默认情况下会输出给父进程的' stderr 除非指定了stdio
    • env {Object} 环境变量
    • uid {Number} 设置用户进程的ID。 (参见setuid(2)。)
    • gid {Number} 设置进程组的ID。 (参见setgid(2)。)
    • timeout {Number} 进程运行最大毫秒数。 (默认: undefined)
    • killSignal {String} 用来终止子进程的信号。 (默认: 'SIGTERM')
    • maxBuffer {Number}
    • encoding {String} stdio输入和输出的编码方式。 (默认: 'buffer')
  • 返回: {Buffer|String}来自命令的stdout

直到子进程完全退出,execSync才会返回。超时或者收到killSignal信号,也不会返回,直到进程完全退出。进程处理完SIGTERM信号后并不会结束,直到子进程完全退出。

如果进程超时,或者非正常退出,这个方法将会抛出异常。Error会包含整个child_process.spawnSync结果。

以上就是Node.js官方文档中有关子进程的介绍。


REPL即Node自带的交互式解释器,它可以实现如下的任务:

  • 读取(Read)- 可以读取用户的输入,解析输入的Javascript数据结构并存储在内存中。
  • 执行(Eval)- 可以执行输入的Javascript数据结构。
  • 打印(Print)- 打印输出结果。
  • 循环(Loop)- 对上述的步骤进行循环,如果需要退出,则用户需要两次按下ctrl-c按钮。
稳定性: 3 - 稳定

Read-Eval-Print-Loop (REPL 读取-执行-输出循环)即可作为独立程序,也可以集成到其他程序中。

REPL提供了一种交互的执行JavaScript并查看输出结果的方法。可以用来调试,测试,或仅是用来试试。

在命令行中不带任何参数的执行node,就是REPL模式。它提供了简单的emacs行编辑。

mjr:~$ nodeType '.help' for options.> a = [ 1, 2, 3];[ 1, 2, 3 ]> a.forEach(function (v) {...   console.log(v);...   });123

若想使用高级的编辑模式,使用环境变量NODE_NO_READLINE=1打开node。这样会开启REPL模式,允许你使用rlwrap

例如,你可以添加以下代码到你的bashrc文件里。

alias node="env NODE_NO_READLINE=1 rlwrap node"

repl.start(options)

启动并返回一个REPLServer实例。它继承自[Readline Interface][]。接收的参数"options"有以下值:

  • prompt- 所有输入输出的提示符和流,默认是>.

  • input- 需要监听的可读流,默认为process.stdin.

  • output- 用来输出数据的可写流,默认为process.stdout.

  • terminal- 如果stream被当成TTY,并且有ANSI/VT100转义,传输true。默认在实例的输出流上检查isTTY

  • eval- 用来对每一行进行求值的函数。默认为eval()的异步封装。参见后面的自定义eval例子。

  • useColors- 写函数输出是否有颜色。如果设定了不同的writer函数则无效。默认为 repl 的terminal值。

  • useGlobal- 如果为true,则repl将会使用全局对象,而不是在独立的上下文中运行scripts。默认为false

  • ignoreUndefined- 如果为true,repl不会输出未定义命令的返回值。默认为false

  • writer- 每个命令行被求值时都会调用这个函数,它会返回格式化显示内容(包括颜色)。默认是util.inspect

如果有以下特性,可以使用自己的eval函数:

function eval(cmd, context, filename, callback) {  callback(null, result);}

在同一个node的运行实例上,可以打开多个REPLs。每个都会共享一个全局对象,但会有独立的I/O。

以下的例子,在stdin、 Unix socket和 TCP socket上开启REPL :

var net = require("net"),    repl = require("repl");connections = 0;repl.start({  prompt: "node via stdin> ",  input: process.stdin,  output: process.stdout});net.createServer(function (socket) {  connections += 1;  repl.start({    prompt: "node via Unix socket> ",    input: socket,    output: socket  }).on('exit', function() {    socket.end();  })}).listen("/tmp/node-repl-sock");net.createServer(function (socket) {  connections += 1;  repl.start({    prompt: "node via TCP socket> ",    input: socket,    output: socket  }).on('exit', function() {    socket.end();  });}).listen(5001);

从命令行运行这个程序,将会在stdin上启动REPL。其他的REPL客户端可能通过Unix socket或TCP socket连接。telnet常用于连接TCP socket,socat用于连接Unix和TCP sockets

从Unix socket-based服务器启动REPL(而非stdin),你可以建立长连接,不用重启它们。

通过net.Servernet.Socket实例运行"full-featured" (terminal) REPL的例子,参见: https://gist.github.com/2209310

通过curl(1)实例运行REPL的例子,参见: https://gist.github.com/2053342

Event: 'exit'

function () {}

当用户通过预定义的方式退出REPL将会触发这个事件。预定义的方式包括,在repl里输入.exit,按Ctrl+C两次来发送SIGINT信号,或者在input流上按Ctrl+D 来发送"end"。

监听exit的例子:

r.on('exit', function () {  console.log('Got "exit" event from repl!');  process.exit();});

Event: 'reset'

function (context) {}

重置REPL的上下文的时候触发。当你输入.clear会重置。如果你用{ useGlobal: true }启动repl,那这个事件永远不会被触发。

监听reset的例子:

// Extend the initial repl context.r = repl.start({ options ... });someExtension.extend(r.context);// When a new context is created extend it as well.r.on('reset', function (context) {  console.log('repl has a new context');  someExtension.extend(context);});

REPL 特性

在REPL里, Control+D会退出。可以输入多行表达式。支持全局变量和局部变量的TAB自动补全。

特殊变量_(下划线)包含上一个表达式的结果。

> [ "a", "b", "c" ][ 'a', 'b', 'c' ]> _.length3> _ += 14

REPL支持在全局域里访问任何变量。将变量赋值个和REPLServer关联的上下文对象,你可以显示的讲变量暴露给REPL,例如:

// repl_test.jsvar repl = require("repl"),    msg = "message";repl.start("> ").context.m = msg;

context对象里的东西,会以局部变量的形式出现:

mjr:~$ node repl_test.js> m'message'

有一些特殊的REPL命令:

  • .break - 当你输入多行表达式时,也许你走神了或者不想完成了,.break可以重新开始。
  • .clear - 重置context对象为空对象,并且清空多行表达式。
  • .exit - 关闭输入/输出流,会让REPL退出。
  • .help - 打印这些特殊命令。
  • .save - 保存当前REPL会话到文件。

    .save ./file/to/save.js

  • .load- 加载一个文件到当前REPL会话

    .load ./file/to/load.js

下面的组合键在REPL中有以下效果:

  • <ctrl>C- 和.break键类似,在一个空行连按两次会强制退出。
  • <ctrl>D- 和.exit键类似。


稳定性: 3 - 稳定

如果要在Node.js中使用HTTP服务器或客户端功能,则必须调用require('http')

Node里的HTTP接口支持协议里原本比较难用的特性。特别是很大的或块编码的消息。这些接口不会完全缓存整个请求或响应,这样用户可以在请求或响应中使用数据流。

HTTP消息头对象和下面的例子类似:

{ 'content-length': '123',  'content-type': 'text/plain',  'connection': 'keep-alive',  'host': 'mysite.com',  'accept': '*/*' }

Keys都是小写,值不能修改。

为了能支持尽可能多的HTTP应用程序,Node提供的HTTP API接口都是底层的。仅能处理流和消息。它把消息解析成报文头和报文体,但是不解析实际的报文头和报文体内容。

定义报文头的时候,多个值间可用,分隔。除了set-cookiecookie头,因为它们表示值的数组。诸如content-length的只有一个值的报文头,直接解析,并且只有单值可以表示成已经解析好的对象。

接收到的原始头信息以数组([key, value, key2, value2, ...])的形式保存在rawHeaders里。例如,之前提到的消息对象会有如下的rawHeaders

[ 'ConTent-Length', '123456',  'content-LENGTH', '123',  'content-type', 'text/plain',  'CONNECTION', 'keep-alive',  'Host', 'mysite.com',  'accepT', '*/*' ]

http.METHODS

  • {Array}

解析器支持的HTTP方法列表。

http.STATUS_CODES

  • {Object}

全部标准HTTP响应状态码的和描述的集合。例如,http.STATUS_CODES[404] === 'Not Found'

http.createServer([requestListener])

返回http.Server的新实例。

requestListener函数自动加到'request'事件里。

http.createClient([port][, host])

这个函数已经被抛弃,用http.request()替换。创建一个新的HTTP客户端,连接到服务器的porthost

类: http.Server

这是事件分发器EventEmitter,有以下事件:

事件: 'request'

function (request, response) { }

每当有请求的时候触发。注意:每个连接可以有多个请求(在keep-alive连接中)。requesthttp.IncomingMessage实例,responsehttp.ServerResponse 的实例。

事件: 'connection'

function (socket) { }

当建立新的TCP流的时候。socket是一个net.Socket对象。通常用户不会访问这个事件。协议解析器绑定套接字时采用的方式使套接字不会出发readable事件。也能通过request.connection访问socket

事件: 'close'

function () { }

服务器关闭的时候触发。

事件: 'checkContinue'

function (request, response) { }

当http收到100-continue的http请求时会触发。如果没有监听这个事件,服务器将会自动发送100 Continue的响应。

如果客户端需要继续发送请求主题,或者生成合适的HTTP响应(如,400请求无效),可以通过调用response.writeContinue()来处理。

注意:触发并处理这个事件的时候,不会再触发request事件。

事件: 'connect'

function (request, socket, head) { }

当客户端请求http连接时触发。如果没有监听这个事件,客户端请求连接的时候会被关闭。

  • request是http请求的参数,与request事件参数相同。
  • socket是服务器和客户端间的socket。
  • head是buffer的实例。网络隧道的第一个包,可能为空。

这个事件触发后,请求的socket不会有data事件监听器,也就是说你需要绑定一个监听器到data上,来处理在发送到服务器上的socket数据。

事件: 'upgrade'

function (request, socket, head) { }

当客户端请求http upgrage时候会触发。如果没有监听这个事件,客户端请求一个连接的时候会被关闭。

  • request是http请求的参数,与request事件参数相同。
  • socket是服务器和客户端间的socket。
  • head是buffer的实例。网络隧道的第一个包,可能为空。

这个事件触发后,请求的socket不会有data事件监听器,也就是说你需要绑定一个监听器到data上,来处理在发送到服务器上的socket数据。

事件: 'clientError'

function (exception, socket) { }

如果一个客户端连接触发了一个'error'事件,它就会转发到这里.

socket是导致错误的net.Socket对象。

server.listen(port[, hostname][, backlog][, callback])

在指定的的端口和主机名上开始接收连接。如果忽略主机名,服务器将会接收指向任意的IPv4地址(INADDR_ANY)。

监听一个unix socket,需要提供一个文件名而不是主机名和端口。

积压量backlog为等待连接队列的最大长度。实际的长度由你的操作系统的sysctl设置决定(比如linux上的tcp_max_syn_backlogsomaxconn)。默认参数值为 511 (不是512)

这是异步函数。最后一个参数callback会作为事件监听器添加到listening事件。参见net.Server.listen(port)

server.listen(path[, callback])

启动一个UNIX socket服务器所给路径path

这是异步函数。最后一个参数callback会作为事件监听器添加到listening事件。参见net.Server.listen(port)

server.listen(handle[, callback])

  • handle {Object}
  • callback {Function}

    handle 对象可以是server或socket(任意以下划线_handle开头的成员),或者{fd: <n>}对象。

这会导致server用参数handle接收连接,前提条件是文件描述符或句柄已经连接到端口或域socket。

Windows不能监听文件句柄。

这是异步函数。最后一个参数callback会作为事件监听器添加到listening事件。参见net.Server.listen(port)

server.close([callback])

用于禁止server接收连接。参见net.Server.close().

server.maxHeadersCount

最大请求头的数量限制,默认为1000。如果设置为0,则不做任何限制。

server.setTimeout(msecs, callback)

  • msecs{Number}
  • callback{Function}

为socket设置超时时间,单位为毫秒,如果发生超时,在server对象上触发'timeout'事件,参数为socket。

如果在Server对象上有一个'timeout'事件监听器,超时的时候,将会调用它,参数为socket。

默认情况下,Server的超时为2分钟,如果超时将会销毁socket。如果你给Server的超时事件设置了回调函数,那你就得负责处理socket超时。

server.timeout

  • {Number} Default = 120000 (2 minutes)

超时的时长,单位为毫秒。

注意,socket的超时逻辑在连接时设定,所以有新的连接时才能改变这个值。

设为0时,建立连接的自动超时将失效。

类: http.ServerResponse

这是一个由HTTP服务器(而不是用户)内部创建的对象。作为第二个参数传递给'request'事件。

该响应实现了Writable Stream接口。这是一个包含下列事件的EventEmitter

事件: 'close'

function () { }

在调用response.end(),或准备flush前,底层连接结束。

事件: 'finish'

function () { }

发送完响应触发。响应头和响应体最后一段数据被剥离给操作系统后,通过网络来传输时被触发。这并不代表客户端已经收到数据。

这个事件之后,响应对象不会再触发任何事件。

response.writeContinue()

发送HTTP/1.1 100 Continue消息给客户端,表示请求体可以发送。可以在服务器上查看'checkContinue'事件。

response.writeHead(statusCode[, statusMessage][, headers])

发送一个响应头给请求。状态码是3位数字,如404。最后一个参数headers是响应头。建议第二个参数设置为可以看的懂的消息。

例如:

var body = 'hello world';response.writeHead(200, {  'Content-Length': body.length,  'Content-Type': 'text/plain' });

这个方法仅能在消息中调用一次,而且必须在response.end()前调用。

如果你在这之前调用response.write()response.end(),将会计算出不稳定的头。

Content-Length是字节数,而不是字符数。上面的例子'hello world'仅包含一个字节字符。如果body包含高级编码的字符,则Buffer.byteLength()就必须确定指定编码的字符数。Node不会检查Content-Length和body的长度是否相同。

response.setTimeout(msecs, callback)

  • msecs {Number}
  • callback {Function}

设置socket超时时间,单位为毫秒。如果提供了回调函数,将会在response对象的'timeout'事件上添加监听器。

如果没有给请求、响应、服务器添加'timeout'监视器,超时的时候将会销毁socket。如果你给请求、响应、服务器加了处理函数,就需要负责处理socket超时。

response.statusCode

使用默认的headers 时(没有显式的调用response.writeHead()),这个属性表示将要发送给客户端状态码。

例如:

response.statusCode = 404;

响应头发送给客户端的后,这个属性表示状态码已经发送。

response.statusMessage

使用默认headers时(没有显式的调用response.writeHead()),这个属性表示将要发送给客户端状态信息。如果这个没有定义,将会使用状态码的标准消息。

例如:

response.statusMessage = 'Not found';

当响应头发送给客户端的时候,这个属性表示状态消息已经发送。

response.setHeader(name, value)

设置默认头某个字段内容。如果这个头即将被发送,内容会被替换。如果你想设置更多的头, 就使用一个相同名字的字符串数组。

例如:

response.setHeader("Content-Type", "text/html");

response.setHeader("Set-Cookie", ["type=ninja", "language=javascript"]);

response.headersSent

Boolean(只读)。如果headers发送完毕,则为true,反之为false。

response.sendDate

默认值为true。若为true,当headers里没有Date值时,自动生成Date并发送。

只有在测试环境才能禁用,因为HTTP要求响应包含Date头.

response.getHeader(name)

读取一个在队列中但是还没有被发送至客户端的header。名字是大小写敏感的。仅能再头被flushed前调用。

例如:

var contentType = response.getHeader('content-type');

response.removeHeader(name)

从即将发送的队列里移除头。

例如:

response.removeHeader("Content-Encoding");

response.write(chunk[, encoding][, callback])

如果调用了这个方法,且还没有调用response.writeHead(),将会切换到默认的header,并更新这个header。

这个方法将发送响应体数据块。可能会多次调用这个方法,以提供body成功的部分内容。

chunk可以是字符串或 buffer。如果chunk 是字符串,第二个参数表明如何将它编码成字节流。encoding的默认值是'utf8'。最后一个参数在刷新这个数据块时调用。

注意:这个是原始的HTTP body,和高级的multi-part body编码无关。

第一次调用response.write()的时候,将会发送缓存的头信息和第一个body给客户端。第二次,将会调用response.write()。Node认为你将会独立发送流数据。这意味着,响应缓存在第一个数据块中。

如果成功的刷新全部数据到内核缓冲区,返回true 。如果部分或全部数据在用户内存中还处于排队状况,返回false。当缓存再次释放的时候,将会触发 'drain'

response.addTrailers(headers)

这个方法给响应添加HTTP的尾部header(消息末尾的header)。

只有数据块编码用于响应体时,才会触发Trailers;如果不是(例如,请求是HTTP/1.0),它们将会被自动丢弃。

如果你想触发trailers, HTTP会要求发送Trailer头,它包含一些信息,比如:

response.writeHead(200, { 'Content-Type': 'text/plain',                          'Trailer': 'Content-MD5' });response.write(fileData);response.addTrailers({'Content-MD5': "7895bf4b8828b55ceaf47747b4bca667"});response.end();

response.end([data][, encoding][, callback])

这个方法告诉服务器,所有的响应头和响应体已经发送;服务器可以认为消息结束。response.end()方法必须在每个响应中调用。

如果指定了参数data,将会在响应流结束的时候调用。

http.request(options[, callback])

Node维护每个服务器的连接来生成HTTP请求。这个函数让你可以发布请求。

参数options是对象或字符串。如果options是字符串,会通过url.parse()自动解析。

options值:

  • host: 请求的服务器域名或 IP 地址,默认:'localhost'
  • hostname: 用于支持url.parse()hostname优于host
  • port: 远程服务器端口。 默认为:80.
  • localAddress: 用于绑定网络连接的本地接口
  • socketPath: Unix域socket(使用host:port或socketPath)
  • method: 指定 HTTP 请求方法。 默认: 'GET'.
  • path: 请求路径。 默认为:'/'。如果有查询字符串,则需要包含。例如,'/index.html?page=12'。请求路径包含非法字符时抛出异常。目前,只有空格不行,不过在未来可能改变。
  • headers: 包含请求头的对象
  • auth: 用于计算认证头的基本认证,即user:password
  • agent: 控制Agent的行为。当使用了一个Agent的时候,请求将默认为Connection: keep-alive。可能的值为:
    • undefined (default): 在这个主机和端口上使用global Agent
    • Agent object: 在Agent中显式使用passed .
    • false: 选择性停用连接池,默认请求为:Connection: close.
    • keepAlive: {Boolean} 持资源池周围的socket,用于未来其它请求。默认值为false
    • keepAliveMsecs: {Integer} 使用HTTP KeepAlive的时候,通过正在保持活动的sockets发送TCP KeepAlive包的频繁程度。默认值为1000。仅当keepAlive为true时才相关。

可选参数callback将会作为一次性的监视器,添加给 'response' 事件。

http.request()返回一个http.ClientRequest类的实例。ClientRequest实例是一个可写流对象。如果需要用POST请求上传一个文件的话,就将其写入到ClientRequest对象。

例如:

var postData = querystring.stringify({  'msg' : 'Hello World!'});var options = {  hostname: 'www.google.com',  port: 80,  path: '/upload',  method: 'POST',  headers: {    'Content-Type': 'application/x-www-form-urlencoded',    'Content-Length': postData.length  }};var req = http.request(options, function(res) {  console.log('STATUS: ' + res.statusCode);  console.log('HEADERS: ' + JSON.stringify(res.headers));  res.setEncoding('utf8');  res.on('data', function (chunk) {    console.log('BODY: ' + chunk);  });});req.on('error', function(e) {  console.log('problem with request: ' + e.message);});// write data to request bodyreq.write(postData);req.end();

注意,例子里调用了req.end()http.request()必须调用req.end()来表明请求已经完成,即使没有数据写入到请求body里。

如果在请求的时候遇到错误(DNS解析、TCP级别的错误或实际HTTP解析错误),在返回的请求对象时会触发一个'error'事件。

有一些特殊的头需要注意:

  • 发送Connection: keep-alive告诉服务器保持连接,直到下一个请求到来。

  • 发送Content-length头将会禁用chunked编码。

  • 发送一个Expect头,会立即发送请求头,一般来说,发送Expect: 100-continue,你必须设置超时,并监听continue事件。更多细节参见RFC2616 Section 8.2.3。

  • 发送一个授权头,将会使用auth参数重写,来计算基本的授权。

http.get(options[, callback])

因为多数请求是没有报文体的GET请求,Node提供了这个简便的方法。和http.request()唯一不同点在于,这个方法自动设置GET,并自动调用req.end()

例如:

http.get("http://www.google.com/index.html", function(res) {  console.log("Got response: " + res.statusCode);}).on('error', function(e) {  console.log("Got error: " + e.message);});

类: http.Agent

HTTP Agent用于socket池,用于HTTP客户端请求。

HTTP Agent也把客户端请求默认为使用Connection:keep-alive 。如果没有HTTP请求正在等着成为空闲socket的话,那么socket将关闭。这意味着,Node的资源池在负载的情况下对keep-alive有利,但是仍然不需要开发人员使用KeepAlive来手动关闭HTTP客户端。

如果你选择使用HTTP KeepAlive, 可以创建一个Agent对象,将 flag 设置为true. (参见下面的constructor options ) ,这样Agent会把没用到的socket放到池里,以便将来使用。他们会被显式的标志,让Node不运行。但是,当不再使用它的时候,需要显式的调用destroy(),这样socket将会被关闭。

当socket事件触发close事件或特殊的agentRemove事件时,socket将会从agent池里移除。如果你要保持HTTP请求保持长时间打开,并且不希望他们在池里,可以参考以下代码:

http.get(options, function(res) {  // Do stuff}).on("socket", function (socket) {  socket.emit("agentRemove");});

另外,你可以使用agent:false让资源池停用:

http.get({  hostname: 'localhost',  port: 80,  path: '/',  agent: false  // create a new agent just for this one request}, function (res) {  // Do stuff with response})

new Agent([options])

  • options {Object} agent上的设置选项集合,有以下字段内容:
    • keepAlive {Boolean} 持资源池周围的socket,用于未来其它请求。默认值为false
    • keepAliveMsecs {Integer} 使用HTTP KeepAlive的时候,通过正在保持活动的sockets发送TCP KeepAlive包的频繁程度。默认值为1000。仅当 keepAlive为true时才相关。
    • maxSockets {Number}在空闲状态下,还依然开启的socket的最大值。仅当keepAlive设置为true的时候有效。默认值为256。

http.request使用的默认的http.globalAgent,会设置全部的值为默认。

必须在创建你自己的Agent对象后,才能配置这些值。

var http = require('http');var keepAliveAgent = new http.Agent({ keepAlive: true });options.agent = keepAliveAgent;http.request(options, onResponseCallback);

agent.maxSockets

默认值为Infinity。决定了每台主机上的agent可以拥有的并发socket的打开数量,主机可以是host:porthost:port:localAddress

agent.maxFreeSockets

默认值256.对于支持HTTP KeepAlive的Agent而言,这个方法设置了空闲状态下仍然打开的套接字数的最大值。

agent.sockets

这个对象包含了当前Agent使用中的socket数组。不要修改它。

agent.freeSockets

使用HTTP KeepAlive的时候,这个对象包含等待当前Agent使用的socket数组。不要修改它。

agent.requests

这个对象包含了还没分配给socket的请求数组。不要修改它。

agent.destroy()

销毁任意一个被agent使用的socket。

通常情况下不要这么做。如果你正在使用一个允许KeepAlive的agent,当你知道不在使用它的时候,最好关闭agent。否则,socket会一直保存打开状态,直到服务器关闭。

agent.getName(options)

获取一组请求选项的唯一名,来确定某个连接是否可重用。在http agent里,它会返回host:port:localAddress。在http agent里, name包括CA,cert, ciphers, 和其他HTTPS/TLS特殊选项来决定socket是否可以重用。

http.globalAgent

Agent的全局实例,是http客户端的默认请求。

类:http.ClientRequest

该对象在内部创建并从http.request()返回。他是正在处理的请求,其头部已经在队列中。使用setHeader(name, value),getHeader(name), removeHeader(name)API可以改变header。当关闭连接的时候,header将会和第一个数据块一起发送。

为了获取响应,可以给请求对象的'response'添加监听器。当接收到响应头的时候将会从请求对象里触发'response''response'事件执行时有一个参数,该参数为http.IncomingMessage的实例。

'response'事件期间,可以给响应对象添加监视器,监听'data'事件。

如果没有添加'response'处理函数,响应将被完全忽略。如果你添加了'response'事件处理函数,那你必须消费掉从响应对象获取的数据,可以在 'readable'事件里调用response.read(),或者添加一个'data'处理函数,或者调用.resume()方法。如果未读取数据,它将会消耗内存,最终产生 process out of memory错误。

Node不会检查Content-Length和body的长度是否相同。

该请求实现了Writable Stream接口。这是一个包含下列事件的EventEmitter

事件: 'response'

function (response) { }

当接收到请求的时候会触发,仅会触发一次。response的参数是http.IncomingMessage的实例。

Options:

  • host: 要请求的服务器域名或IP地址
  • port: 远程服务器的端口
  • socketPath: Unix域Socket (使用host:port或socketPath之一)

事件: 'socket'

function (socket) { }

Socket附加到这个请求的时候触发。

事件: 'connect'

function (response, socket, head) { }

每次服务器使用CONNECT方法响应一个请求时触发。如果这个这个事件未被监听,接收CONNECT方法的客户端将关闭他们的连接。

下面的例子展示了一对匹配的客户端/服务器如何监听connect事件。var http = require('http');var net = require('net');var url = require('url');

// Create an HTTP tunneling proxyvar proxy = http.createServer(function (req, res) {  res.writeHead(200, {'Content-Type': 'text/plain'});  res.end('okay');});proxy.on('connect', function(req, cltSocket, head) {  // connect to an origin server  var srvUrl = url.parse('http://' + req.url);  var srvSocket = net.connect(srvUrl.port, srvUrl.hostname, function() {    cltSocket.write('HTTP/1.1 200 Connection Established
' +                    'Proxy-agent: Node-Proxy
' +                    '
');    srvSocket.write(head);    srvSocket.pipe(cltSocket);    cltSocket.pipe(srvSocket);  });});// now that proxy is runningproxy.listen(1337, '127.0.0.1', function() {  // make a request to a tunneling proxy  var options = {    port: 1337,    hostname: '127.0.0.1',    method: 'CONNECT',    path: 'www.google.com:80'  };  var req = http.request(options);  req.end();  req.on('connect', function(res, socket, head) {    console.log('got connected!');    // make a request over an HTTP tunnel    socket.write('GET / HTTP/1.1
' +                 'Host: www.google.com:80
' +                 'Connection: close
' +                 '
');    socket.on('data', function(chunk) {      console.log(chunk.toString());    });    socket.on('end', function() {      proxy.close();    });  });});

事件: 'upgrade'

function (response, socket, head) { }

每当服务器响应upgrade请求时触发。如果没有监听这个事件,客户端会收到upgrade头后关闭连接。

下面的例子展示了一对匹配的客户端/服务器如何监听upgrade事件。

var http = require('http');// Create an HTTP servervar srv = http.createServer(function (req, res) {  res.writeHead(200, {'Content-Type': 'text/plain'});  res.end('okay');});srv.on('upgrade', function(req, socket, head) {  socket.write('HTTP/1.1 101 Web Socket Protocol Handshake
' +               'Upgrade: WebSocket
' +               'Connection: Upgrade
' +               '
');  socket.pipe(socket); // echo back});// now that server is runningsrv.listen(1337, '127.0.0.1', function() {  // make a request  var options = {    port: 1337,    hostname: '127.0.0.1',    headers: {      'Connection': 'Upgrade',      'Upgrade': 'websocket'    }  };  var req = http.request(options);  req.end();  req.on('upgrade', function(res, socket, upgradeHead) {    console.log('got upgraded!');    socket.end();    process.exit(0);  });});

事件: 'continue'

function () { }

当服务器发送'100 Continue' HTTP响应的时候触发,通常因为请求包含'Expect: 100-continue'。该指令表示客户端应发送请求体。

request.flushHeaders()

刷新请求的头。

考虑效率因素,Node.js通常会缓存请求的头直到你调用request.end(),或写入请求的第一个数据块。然后,包装请求的头和数据到一个独立的TCP包里。

request.write(chunk[, encoding][, callback])

发送一个请求体的数据块。通过多次调用这个函数,用户能流式的发送请求给服务器,这种情况下,建议使用['Transfer-Encoding', 'chunked']头。

chunk参数必须是Buffer或字符串。

回调参数可选,当这个数据块被刷新的时候会被调用。

request.end([data][, encoding][, callback])

发送请求完毕。如果body的数据没被发送,将会将他们刷新到流里。如果请求是分块的,该方法会发送终结符0 。

如果指定了data,等同于先调用request.write(data, encoding),再调用request.end(callback)

如果有callback,将会在请求流结束的时候调用。

request.abort()

终止一个请求. (v0.3.8开始新加入)。

request.setTimeout(timeout[, callback])

如果socket被分配给这个请求,并完成连接,将会调用socket.setTimeout()

request.setNoDelay([noDelay])

如果socket被分配给这个请求,并完成连接,将会调用socket.setNoDelay()

request.setSocketKeepAlive([enable][, initialDelay])

如果socket被分配给这个请求,并完成连接,将会调用socket.setKeepAlive()

http.IncomingMessage

http.Serverhttp.ClientRequest创建了IncomingMessage对象,作为第一个参数传递给'response'。它可以用来访问应答的状态,头文件和数据。

它实现了Readable Stream接口,以及以下额外的事件,方法和属性。

事件: 'close'

function () { }

表示底层连接已经关闭。和'end'类似,这个事件每个应答只会发送一次。

message.httpVersion

客户端向服务器发送请求时,客户端发送的 HTTP 版本;或者服务器想客户端返回应答时,服务器的HTTP版本。通常是'1.1''1.0'

另外,response.httpVersionMajor是第一个整数,response.httpVersionMinor是第二个整数。

message.headers

请求/响应头对象。

只读的头名称和值的映射。头的名字是小写,比如:

// Prints something like://// { 'user-agent': 'curl/7.22.0',//   host: '127.0.0.1:8000',//   accept: '*/*' }console.log(request.headers);

message.rawHeaders

接收到的请求/响应头字段列表。

注意,键和值在同一个列表中。它并非一个元组列表。所以,偶数偏移量为键,奇数偏移量为对应的值。

头名字不是小写敏感,也没用合并重复的头。

// Prints something like://// [ 'user-agent',//   'this is invalid because there can be only one',//   'User-Agent',//   'curl/7.22.0',//   'Host',//   '127.0.0.1:8000',//   'ACCEPT',//   '*/*' ]console.log(request.rawHeaders);

message.trailers

请求/响应的尾部对象。只在'end'事件中存在。

message.rawTrailers

接收到的原始的请求/响应尾部键和值。仅在'end'事件中存在。

message.setTimeout(msecs, callback)

  • msecs {Number}
  • callback {Function}

调用message.connection.setTimeout(msecs, callback).

message.method

仅对从http.Server获得的请求有效。

请求方法如果一个只读的字符串。例如:'GET','DELETE'.

message.url

仅对从http.Server获得的请求有效。

请求的URL字符串。它仅包含实际的HTTP请求中所提供的URL,比如请求如下:

GET /status?name=ryan HTTP/1.1
Accept: text/plain

request.url 就是:

'/status?name=ryan'

如果你想将URL分解,可以用require('url').parse(request.url),例如:

node> require('url').parse('/status?name=ryan'){ href: '/status?name=ryan',  search: '?name=ryan',  query: 'name=ryan',  pathname: '/status' }

如果想从查询字符串中解析出参数,可以用require('querystring').parse函数,或者将true作为第二个参数传递给require('url').parse。 例如:

node> require('url').parse('/status?name=ryan', true){ href: '/status?name=ryan',  search: '?name=ryan',  query: { name: 'ryan' },  pathname: '/status' }

message.statusCode

仅对从http.ClientRequest获取的响应有效。

3位数的HTTP响应状态码404

message.statusMessage

仅对从http.ClientRequest获取的响应有效。

HTTP的响应消息。比如,OKInternal Server Error.

message.socket

和连接相关联的net.Socket对象。

通过HTTPS支持,使用request.connection.verifyPeer()request.connection.getPeerCertificate()获取客户端的身份信息。


稳定性: 2 - 不稳定

单个Node.js实例在单线程中运行,在某些情况下,它可能出现负载,因此为了能够更好的利用多核系统的能力,你可以使用Node.js内置的集群(cluster)功能来处理负载。

在集群模块里很容易就能创建一个共享所有服务器接口的进程。

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  // In this case its a HTTP server  http.createServer(function(req, res) {    res.writeHead(200);    res.end("hello world
");  }).listen(8000);}

运行Node后,将会在所有工作进程里共享8000端口。

% NODE_DEBUG=cluster node server.js23521,Master Worker 23524 online23521,Master Worker 23526 online23521,Master Worker 23523 online23521,Master Worker 23528 online

这个特性是最近才引入的,大家可以试试并提供反馈。

还要注意,在Windows系统里还不能在工作进程中创建一个被命名的管道服务器。

如何工作

child_process.fork方法派生工作进程,所以它能通过IPC和父进程通讯,并相互传递句柄。

集群模块通过以下的两种分发模式来处理连接:

第一种(默认方法,除了Windows平台)为循环式。主进程监听一个端口,接收新的连接,再轮流的分发给工作进程。

第二种,主进程监听socket,并发送给感兴趣的工作进程,工作进程直接接收连接。

比较上述两种方法,第二种方法理论上性能最高。实际上,由于操作系统各式各样,分配往往分配不均。比如,70%的连接终止于2个进程,实际上共有8个进程。

因为server.listen()将大部分工作交给了主进程,所以一个普通的Node.js进程和一个集群工作进程会在三种情况下有所区别:

  1. server.listen({fd: 7})由于消息被传回主进程,所以将会监听主进程里的文件描述符,而不是其他工作进程里的文件描述符 7。
  2. server.listen(handle)监听一个明确地句柄,会使得工作进程使用指定句柄,而不是与主进程通讯。如果工作进程已经拥有了该句柄,前提是您知道在做什么。
  3. server.listen(0)通常它会让服务器随机监听端口。然而在集群里每个工作进程listen(0)时会收到相同的端口。实际上仅第一次是随机的,之后是可预测的。如果你想监听一个特定的端口,可以根据集群的工作进程的ID生产一个端口ID 。

在Node.js或你的程序里没有路由逻辑,工作进程见也没有共享状态。因此,像登录和会话这样的工作,不要设计成过度依赖内存里的对象。

因为工作线程都是独立的,你可以根据需求来杀死或者派生而不会影响其他进程。只要仍然有工作进程,服务器还会接收连接。Node不会自动管理工作进程的数量,这是你的责任,你可以根据自己需求来管理。

cluster.schedulingPolicy

调度策略cluster.SCHED_RR表示轮流制,cluster.SCHED_NONE表示操作系统处理。这是全局性的设定,一旦你通过cluster.setupMaster()派生了第一个工作进程,它就不可更改了。

SCHED_RR是除Windows外所有系统的默认设置。只要libuv能够有效地分配IOCP句柄并且不产生巨大的性能损失,Windows也会改为SCHED_RR方式。

cluster.schedulingPolicy也可通过环境变量NODE_CLUSTER_SCHED_POLICY来更改。有效值为"rr""none"

cluster.settings

  • {Object}
    • execArgv {Array} 传给可执行的Node的参数列表(默认=process.execArgv)
    • exec {String} 执行文件的路径。 (默认=process.argv[1])
    • args {Array} 传给工作进程的参数列表(默认=process.argv.slice(2))
    • silent {Boolean}是否将输出发送给父进程的stdio。(默认=false)
    • uid {Number} 设置用户进程的ID。 (参考setuid(2)。)
    • gid {Number} 设置进程组的ID。 (参考setgid(2)。)

调用.setupMaster()(或.fork())方法后,这个settings对象会包含设置内容,包括默认值。

设置后会立即冻结,因为.setupMaster()只能调用一次。

这个对象不应该被手动改变或设置。

cluster.isMaster

  • {Boolean}

如果是主进程,返回true。如果process.env.NODE_UNIQUE_ID未定义,则isMastertrue

cluster.isWorker

  • {Boolean}

如果不是主进程返回true(和cluster.isMaster相反)。

事件: 'fork'

  • worker {Worker object}

当一个新的工作进程被分支出来,集群模块会产生'fork'事件。它可用于记录工作进程,并创建自己的超时管理。

var timeouts = [];function errorMsg() {  console.error("Something must be wrong with the connection ...");}cluster.on('fork', function(worker) {  timeouts[worker.id] = setTimeout(errorMsg, 2000);});cluster.on('listening', function(worker, address) {  clearTimeout(timeouts[worker.id]);});cluster.on('exit', function(worker, code, signal) {  clearTimeout(timeouts[worker.id]);  errorMsg();});

事件: 'online'

  • worker {Worker object}

分支出一个新的工作进程后,它会响应在线消息。当主线程接收到在线消息后,它会触发这个事件。'fork'和'online'之间的区别在于,主进程分支一个工作进程后会调用 fork,而工作进程运行后会调用emitted。

cluster.on('online', function(worker) {  console.log("Yay, the worker responded after it was forked");});

事件: 'listening'

  • worker {Worker object}
  • address {Object}

工作进程调用listen()时,服务器会触发'listening'事件,同时也会在主进程的集群里触发。

事件处理函数有两个参数,worker包含工作进程对象,address包含以下属性:address, portaddressType。如果工作进程监听多个地址的时候,这些东西非常有用。

cluster.on('listening', function(worker, address) {  console.log("A worker is now connected to " + address.address + ":" + address.port);});

addressType是以下内容:

  • 4 (TCPv4)
  • 6 (TCPv6)
  • -1 (unix domain socket)
  • "udp4" 或者"udp6"(UDP v4或者v6)*

事件: 'disconnect'

  • worker {Worker object}

当一个工作进程的IPC通道关闭时会触发这个事件。当工作进程正常退出,被杀死,或者手工关闭(例如worker.disconnect())时会调用。

disconnectexit事件间可能存在延迟。 这些事件可以用来检测进程是否卡在清理过程中,或者存在长连接。

cluster.on('disconnect', function(worker) {  console.log('The worker #' + worker.id + ' has disconnected');});

事件: 'exit'

  • worker {Worker object}
  • code {Number} 如果正常退出,则为退出代码.
  • signal {String} 使得进程被杀死的信号名 (比如,'SIGHUP')

当任意一个工作进程终止的时候,集群模块会触发'exit'事件。

可以调用.fork()重新启动工作进程。

cluster.on('exit', function(worker, code, signal) {  console.log('worker %d died (%s). restarting...',    worker.process.pid, signal || code);  cluster.fork();});

参见child_process event: 'exit'.

事件: 'setup'

  • settings{Object}

调用.setupMaster()后会被触发。

settings对象就是cluster.settings对象。

详细内容参见cluster.settings

cluster.setupMaster([settings])

  • settings {Object}
    • exec {String} 执行文件的路径。 (默认=process.argv[1])
    • args {Array}传给工作进程的参数列表(默认=process.argv.slice(2))
    • silent {Boolean} 是否将输出发送给父进程的stdio.

setupMaster用来改变默认的'fork' 。 一旦调用,settings值将会出现在cluster.settings里。

你需要注意如下事项:

  • 改变任何设置,仅会对未来的工作进程产生影响,不会影响对目前已经运行的进程
  • 工作进程里,仅能改变传递给.fork()env属性。
  • 以上的默认值,仅在第一次调用的时候有效,之后的默认值是调用cluster.setupMaster()后的值。

例如:

var cluster = require('cluster');cluster.setupMaster({  exec: 'worker.js',  args: ['--use', 'https'],  silent: true});cluster.fork(); // https workercluster.setupMaster({  args: ['--use', 'http']});cluster.fork(); // http worker

仅能在主进程里调用。

cluster.fork([env])

  • env{Object} 添加到子进程环境变量中的键值。
  • return {Worker object}

派生一个新的工作进程。

仅能在主进程里调用。

cluster.disconnect([callback])

  • callback {Function} 当所有工作进程都断开连接,并且关闭句柄后被调用。

cluster.workers里的每个工作进程可调用.disconnect()关闭。

关闭所有的内部句柄连接,并且没有任何等待处理的事件时,允许主进程优雅的退出。

这个方法有一个可选参数,会在完成时被调用。

仅能在主进程里调用。

cluster.worker

  • {Object}

对当前工作进程对象的引用。主进程中不可用。

var cluster = require('cluster');if (cluster.isMaster) {  console.log('I am master');  cluster.fork();  cluster.fork();} else if (cluster.isWorker) {  console.log('I am worker #' + cluster.worker.id);}

cluster.workers

  • {Object}

存储活跃工作对象的哈希表,主键是id,能方便的遍历所有工作进程,仅在主进程可用。

当工作进程关闭连接并退出后,将会从cluster.workers里移除。这两个事件的次序无法确定,仅能保证从cluster.workers移除会发生在'disconnect''exit'之后。

// Go through all workersfunction eachWorker(callback) {  for (var id in cluster.workers) {    callback(cluster.workers[id]);  }}eachWorker(function(worker) {  worker.send('big announcement to all workers');});

如果希望通过通讯通道引用工作进程,那么使用工作进程的 id 来查询最简单。

socket.on('data', function(id) {  var worker = cluster.workers[id];});

Class: Worker

一个Worker对象包含工作进程所有公开的信息和方法。在主进程里可用通过cluster.workers来获取,在工作进程里可以通过cluster.worker来获取。

worker.id

  • {String}

每一个新的工作进程都有独立的唯一标示,它就是id

当工作进程可用时,id就是cluster.workers里的主键。

worker.process

  • {ChildProcess object}

所有工作进程都是通用child_process.fork()创建的,该函数返回的对象被储存在process中。

参见: Child Process module

注意:当process.suicide不是true的时候,会触发'disconnect'事件,并使得工作进程调用process.exit(0)。它会保护意外的连接关闭。

worker.suicide

  • {Boolean}

调用.kill().disconnect()后设置,在这之前是undefined

worker.suicide能让你区分出是自愿的还是意外退出,主进程可以根据这个值,来决定是否是重新派生成工作进程。

cluster.on('exit', function(worker, code, signal) {  if (worker.suicide === true) {    console.log('Oh, it was just suicide' – no need to worry').  }});// kill workerworker.kill();

worker.send(message[, sendHandle])

  • message {Object}
  • sendHandle {Handle object}

这个函数和child_process.fork()提供的send方法相同。主进程里你必须使用这个函数给指定工作进程发消息。

在工作进程里,你也可以用process.send(message)

这个例子会回应所有来自主进程的消息:

if (cluster.isMaster) {  var worker = cluster.fork();  worker.send('hi there');} else if (cluster.isWorker) {  process.on('message', function(msg) {    process.send(msg);  });}

worker.kill([signal='SIGTERM'])

  • signal{String}发送给工作进程的杀死信号的名称

这个函数会杀死工作进程。在主进程里,它会关闭worker.process,一旦关闭会发送杀死信号。在工作进程里,关闭通道,退出,返回代码0

会导致.suicide被设置。

为了保持兼容性,这个方法的别名是worker.destroy()

注意,在工作进程里有process.kill(),于此不同。

worker.disconnect()

在工作进程里,这个函数会关闭所有服务器,等待 'close' 事件,关闭IPC通道。

在主进程里,发给工作进程一个内部消息,用来调用.disconnect()

会导致.suicide被设置。

注意,服务器关闭后,不再接受新的连接,但可以接受新的监听。已经存在的连接允许正常退出。当连接为空得时候,工作进程的IPC通道运行优雅的退出。

以上仅能适用于服务器的连接,客户端的连接由工作进程关闭。

注意,在工作进程里,存在process.disconnect,但并不是这个函数,它是disconnect。

由于长连接可能会阻塞进程关闭连接,有一个较好的办法是发消息给应用,这样应用会想办法关闭它们。超时管理也是不错,如果超过一定时间后还没有触发 disconnect事件,将会杀死进程。

if (cluster.isMaster) {  var worker = cluster.fork();  var timeout;  worker.on('listening', function(address) {    worker.send('shutdown');    worker.disconnect();    timeout = setTimeout(function() {      worker.kill();    }, 2000);  });  worker.on('disconnect', function() {    clearTimeout(timeout);  });} else if (cluster.isWorker) {  var net = require('net');  var server = net.createServer(function(socket) {    // connections never end  });  server.listen(8000);  process.on('message', function(msg) {    if(msg === 'shutdown') {      // initiate graceful close of any connections to server    }  });}

worker.isDead()

工作进程结束,返回true, 否则返回false

worker.isConnected()

当工作进程通过IPC通道连接主进程时,返回true ,否则false。工作进程创建后会连接到主进程。当disconnect事件触发后会关闭连接。

事件: 'message'

  • message{Object}

该事件和child_process.fork()所提供的一样。在主进程中您应当使用该事件,而在工作进程中您也可以使用process.on('message')

例如,有一个集群使用消息系统在主进程中统计请求的数量:

var cluster = require('cluster');var http = require('http');if (cluster.isMaster) {  // Keep track of http requests  var numReqs = 0;  setInterval(function() {    console.log("numReqs =", numReqs);  }, 1000);  // Count requestes  function messageHandler(msg) {    if (msg.cmd && msg.cmd == 'notifyRequest') {      numReqs += 1;    }  }  // Start workers and listen for messages containing notifyRequest  var numCPUs = require('os').cpus().length;  for (var i = 0; i < numCPUs; i++) {    cluster.fork();  }  Object.keys(cluster.workers).forEach(function(id) {    cluster.workers[id].on('message', messageHandler);  });} else {  // Worker processes have a http server.  http.Server(function(req, res) {    res.writeHead(200);    res.end("hello world
");    // notify master about the request    process.send({ cmd: 'notifyRequest' });  }).listen(8000);}

事件: 'online'

cluster.on('online')事件类似, 仅能在特定工作进程里触发。

cluster.fork().on('online', function() {  // Worker is online});

不会在工作进程里触发。

事件: 'listening'

  • address{Object}

cluster.on('listening')事件类似, 仅能在特定工作进程里触发。

cluster.fork().on('listening', function(address) {  // Worker is listening});

不会在工作进程里触发。

事件: 'disconnect'

cluster.on('disconnect')事件类似, 仅能在特定工作进程里触发。

cluster.fork().on('disconnect', function() {  // Worker has disconnected});

事件: 'exit'

  • code {Number} 正常退出时的退出代码.
  • signal {String} 使得进程被终止的信号的名称(比如SIGHUP)。

cluster.on('exit')事件类似, 仅能在特定工作进程里触发。

var worker = cluster.fork();worker.on('exit', function(code, signal) {  if( signal ) {    console.log("worker was killed by signal: "+signal);  } else if( code !== 0 ) {    console.log("worker exited with error code: "+code);  } else {    console.log("worker success!");  }});

事件: 'error'

child_process.fork()事件类似。

工作进程里,你也可以用process.on('error')


稳定性: 1 - 试验

类: smalloc

表示能够通过简单的内存分配器(处理扩展原始内存的分配)支持的缓存,可供Smalloc使用的函数如下所示:

smalloc.alloc(length[, receiver][, type])

  • length {Number} <= smalloc.kMaxLength
  • receiver {Object} 默认: new Object
  • type {Enum} 默认: Uint8

此函数的作用为返回包含分配的外部数组数据的receiver对象。如果没有传入receiver,则将会创建并返回一个新的对象。

这可以用来创建你自己的类似buffer的类。不会设置其他属性,因此使用者需要跟踪其他所需信息(比如分配的长度)。

function SimpleData(n) {  this.length = n;  smalloc.alloc(this.length, this);}SimpleData.prototype = { /* ... */ };

仅检查receiver是否是非数组的对象。因此,可以分配扩展数据数据,不仅是普通对象。

function allocMe() { }smalloc.alloc(3, allocMe);// { [Function allocMe] '0': 0, '1': 0, '2': 0 }

v8不支持给数组分配扩展数组对象,如果这么做,将会抛出。

你可以指定外部数组数据的类型,在smalloc.Types列出了可供使用的外部数组数据的类型,例如:

var doubleArr = smalloc.alloc(3, smalloc.Types.Double);for (var i = 0; i < 3; i++)  doubleArr = i / 10;// { '0': 0, '1': 0.1, '2': 0.2 }

使用Object.freeze,Object.sealObject.preventExtensions不能冻结、封锁、阻止对象的使用扩展数据扩展。

smalloc.copyOnto(source, sourceStart, dest, destStart, copyLength);

  • source {Object} 分配了外部数组的对象
  • sourceStart {Number} 负责的起始位置
  • dest {Object} 分配了外部数组的对象
  • destStart {Number} 拷贝到目标的起始位置
  • copyLength {Number} 需要拷贝的长度

将内存从一个外部数组分配复制到另一个数组中,任何参数都是可选的,否则会抛出异常。

var a = smalloc.alloc(4);var b = smalloc.alloc(4);for (var i = 0; i < 4; i++) {  a[i] = i;  b[i] = i * 2;}// { '0': 0, '1': 1, '2': 2, '3': 3 }// { '0': 0, '1': 2, '2': 4, '3': 6 }smalloc.copyOnto(b, 2, a, 0, 2);// { '0': 4, '1': 6, '2': 2, '3': 3 }

copyOnto将自动检测内部分配的长度,因此不需要设置任何附加参数。

smalloc.dispose(obj)

  • obj Object

释放通过smalloc.alloc给对象分配的内存。

var a = {};smalloc.alloc(3, a);// { '0': 0, '1': 0, '2': 0 }smalloc.dispose(a);// {}

这对于减轻垃圾回收器的负担是有效的,但是在开发的时候还是要小心,程序里可能会出现难以跟踪的错误。

var a = smalloc.alloc(4);var b = smalloc.alloc(4);// perform this somewhere along the linesmalloc.dispose(b);// now trying to copy some data outsmalloc.copyOnto(b, 2, a, 0, 2);// now results in:// RangeError: copy_length > source_length

调用dispose(),对象依旧拥有外部数据,例如smalloc.hasExternalData()会返回truedispose()不支持缓存,如果传入将会抛出。

smalloc.hasExternalData(obj)

  • obj {Object}

如果obj拥有外部分配的内存,返回true

smalloc.kMaxLength

可分配的最大数量。则同样适用于缓冲区的创建。

smalloc.Types

外部数组的类型,包含:

  • Int8
  • Uint8
  • Int16
  • Uint16
  • Int32
  • Uint32
  • Float
  • Double
  • Uint8Clamped


稳定性: 3 - 稳定

HTTPS是什么?HTTPS是基于TLS/SSL的HTTP协议,在Node.js里它可以作为单独的模块来实现。在本文中,你将了解HTTPS的使用方法。

类: https.Server

https.Server是tls.Server的子类,并且和http.Server一样触发事件。更多信息参见http.Server

server.setTimeout(msecs, callback)

详情参见http.Server#setTimeout().

server.timeout

详情参见http.Server#timeout.

https.createServer(options[, requestListener])

返回一个新的HTTPS服务器对象。其中options类似于 [tls.createServer()][tls.md#tls_tls_createserver_options_secureconnectionlistener]。 requestListener函数自动加到'request'事件里。

例如:

// curl -k https://localhost:8000/var https = require('https');var fs = require('fs');var options = {  key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),  cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem')};https.createServer(options, function (req, res) {  res.writeHead(200);  res.end("hello world
");}).listen(8000);

或:

var https = require('https');var fs = require('fs');var options = {  pfx: fs.readFileSync('server.pfx')};https.createServer(options, function (req, res) {  res.writeHead(200);  res.end("hello world
");}).listen(8000);

server.listen(port[, host][, backlog][, callback])

server.listen(path[, callback])

server.listen(handle[, callback])

详情参见http.listen()

server.close([callback])

详情参见http.close()

https.request(options, callback)

可以给安全web服务器发送请求。

options可以是一个对象或字符串。如果options是字符串,则会被url.parse()解析。

所有来自http.request()选项都是经过验证的。

例如:

var https = require('https');var options = {  hostname: 'encrypted.google.com',  port: 443,  path: '/',  method: 'GET'};var req = https.request(options, function(res) {  console.log("statusCode: ", res.statusCode);  console.log("headers: ", res.headers);  res.on('data', function(d) {    process.stdout.write(d);  });});req.end();req.on('error', function(e) {  console.error(e);});

option参数具有以下的值:

  • host: 请求的服务器域名或IP地址,默认:'localhost'
  • hostname: 用于支持url.parse()hostname优于host
  • port: 远程服务器端口。默认: 443。
  • method: 指定HTTP请求方法。默认:'GET'
  • path: 请求路径。默认:'/'。如果有查询字符串,则需要包含。比如'/index.html?page=12'
  • headers: 包含请求头的对象
  • auth: 用于计算认证头的基本认证,即user:password
  • agent: 控制Agent的行为。当使用了一个Agent的时候,请求将默认为Connection: keep-alive。可能的值为:
    • undefined (default): 在这个主机和端口上使用[global Agent][]
    • Agent object: 在Agent中显式使用passed.
    • false: 选择性停用连接池,默认请求为:Connection: close

tls.connect()的参数也能指定。但是,globalAgent会忽略他们。

  • pfx: SSL使用的证书,私钥,和证书Certificate,默认为null.
  • key: SSL使用的私钥. 默认为null.
  • passphrase: 私钥或pfx的口令字符串. 默认为null.
  • cert: 所用公有x509证书. 默认为null.
  • ca: 用于检查远程主机的证书颁发机构或包含一系列证书颁发机构的数组。
  • ciphers: 描述要使用或排除的密码的字符串,格式请参阅http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT
  • rejectUnauthorized: 如为true则服务器证书会使用所给CA列表验证。如果验证失败则会触发error事件。验证过程发生于连接层,在HTTP请求发送之前。默认为true.
  • secureProtocol: 所用的SSL方法,比如TLSv1_method强制使用TLS version 1。可取值取决于您安装的OpenSSL,和定义于SSL_METHODS的常量。

要指定这些选项,使用一个自定义Agent

例如:

var options = {  hostname: 'encrypted.google.com',  port: 443,  path: '/',  method: 'GET',  key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),  cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem')};options.agent = new https.Agent(options);var req = https.request(options, function(res) {  ...}

或者不使用Agent.

例如:

var options = {  hostname: 'encrypted.google.com',  port: 443,  path: '/',  method: 'GET',  key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),  cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem'),  agent: false};var req = https.request(options, function(res) {  ...}

https.get(options, callback)

http.get()类似,不过是HTTPS版本的.

options可以是字符串对象. 如果options是字符串, 会自动使用url.parse()解析。

例如:

var https = require('https');https.get('https://encrypted.google.com/', function(res) {  console.log("statusCode: ", res.statusCode);  console.log("headers: ", res.headers);  res.on('data', function(d) {    process.stdout.write(d);  });}).on('error', function(e) {  console.error(e);});

类: https.Agent

HTTPS的Agent对象,和http.Agent类似。详情参见https.request()

https.globalAgent

所有HTTPS客户端请求的https.Agent全局实例。


稳定性: 4 - 冻结
  • {Object}

Node.js的console模块提供了一个简单的调试控制台。

Node.js控制台的作用是可以将输出字符打印到stdout(标准输出)和stderr(标准错误)。类似于大部分浏览器提供的console对象函数,Node也是输出到stdout和 stderr。

如果输出目标是终端或文件的时候,console函数是同步的(这是为了防止意外的退出而导致数据丢失),输出是管道的时候是异步的(防止阻塞时间太长)。

下面的例子里,stdout是非阻塞的,而stderr是阻塞的:

$ node script.js 2> error.log | tee info.log

平常使用过程中,只有发现大批量的数据时,才会考虑阻塞或非阻塞问题。

console.log([data][, ...])

输出到stdout并新起一行。和printf()类似,stdout可以传入多个参数,例如:

var count = 5;console.log('count: %d', count);// prints 'count: 5'

如果第一个字符里没有找到格式化的元素,util.inspect将会应用到各个参数,参见util.format()

console.info([data][, ...])

参见console.log

console.error([data][, ...])

参见console.log,不同的是打印到stderr。

console.warn([data][, ...])

参见console.error

console.dir(obj[, options])

obj使用util.inspect,并打印结果到stdout,而这个函数绕过inspect()options参数可能传入以下几种:

  • showHidden- 如果是true,将会展示对象的非枚举属性,默认是false

  • depth- inspect对象递归的次数,对于复杂对象的扫描非常有用。默认是2。想要严格递归,传入null

  • colors- 如果是true,输出会格式化为 ANSI 颜色代码。默认是false。颜色可以定制,下面会介绍。

console.time(label)

标记一个时间点。

console.timeEnd(label)

计时器结束的时候,记录输出,例如:

console.time('100-elements');for (var i = 0; i < 100; i++) {  ;}console.timeEnd('100-elements');// prints 100-elements: 262ms

console.trace(message[, ...])

输出当前位置的栈跟踪到stderr'Trace :'

console.assert(value[, message][, ...])

assert.ok()类似, 但是错误的输出格式为:util.format(message...)


稳定性: 5 - 锁定

本节介绍Node.js的模块系统。

Node.js有简单的模块加载系统。在Node.js模块系统中,每个文件都可以被当作单独的模块。下面例子里,foo.js对同一个文件夹里的circle.js模块进行加载。这是foo.js内容:

var circle = require('./circle.js');console.log( 'The area of a circle of radius 4 is '           + circle.area(4));

这是circle.js内容:

var PI = Math.PI;exports.area = function (r) {  return PI * r * r;};exports.circumference = function (r) {  return 2 * PI * r;};

circle.js模块输出了area()circumference()函数。想要给根模块添加函数和对象,你可以将他们添加到特定的exports对象。

加载到模块的变量是私有的,仿佛模块是包含在一个函数里。在这个例子里,PIcircle.js的私有变量。

如果你想模块里的根像一个函数一样的输出(比如,构造函数),或者你想输出一个完整对象,那就分派给module.exports,而不是exports

bar.js使用square模块,它输出了构造函数:

var square = require('./square.js');var mySquare = square(2);console.log('The area of my square is ' + mySquare.area());

square定义在square.js文件里:

// assigning to exports will not modify module, must use module.exportsmodule.exports = function(width) {  return {    area: function() {      return width * width;    }  };}

模块系统在require("module")模块里实现。

Cycles

环形调用require(),当返回时模块可能都没执行结束。

考虑以下场景:

a.js:

console.log('a starting');exports.done = false;var b = require('./b.js');console.log('in a, b.done = %j', b.done);exports.done = true;console.log('a done');

b.js:

console.log('b starting');exports.done = false;var a = require('./a.js');console.log('in b, a.done = %j', a.done);exports.done = true;console.log('b done');

main.js:

console.log('main starting');var a = require('./a.js');var b = require('./b.js');console.log('in main, a.done=%j, b.done=%j', a.done, b.done);

main.js加载a.jsa.js加载b.js。此时,b.js试着加载a.js。为了阻止循环调用,a.js输出对象的不完全拷贝返回给b.js模块。b.js会结束加载,并且它的exports对象提供给a.js模块。

main.js加载完两个模块时,它们都会结束。这个程序的输出如下:

$ node main.jsmain startinga startingb startingin b, a.done = falseb donein a, b.done = truea donein main, a.done=true, b.done=true

如果你的程序有环形模块依赖,需要保证是线性的。

核心模块

Node有很多模块编译成二进制。这些模块在本文档的其他地方有更详细的描述。

核心模块定义在Node的源代码lib/目录里。

require()总是会优先加载核心模块。例如,require('http')总是返回编译好的HTTP模块,而不管这个文件的名字。

文件模块

如果按照文件名没有找到模块,那么Node会试着加载添加了.js.json后缀的文件,如果还没好到,再试着加载添加了后缀.node的文件。

.js会解析为JavaScript的文本文件,.json会解析为JSON文本文件,.node会解析为编译过的插件模块,由dlopen负责加载。

模块的前缀'/'表示绝对路径。例如require('/home/marco/foo.js')将会加载 /home/marco/foo.js文件。

模块的前缀'./'表示相对于调用require()的路径。就是说,circle.js必须和foo.js在同一个目录里,require('./circle')才能找到。

文件前没有/./前缀,表示模块可能是core module,或者已经从node_modules文件夹里加载过了。

如果指定的路径不存在,require()将会抛出一个code属性为'MODULE_NOT_FOUND'的异常。

node_modules目录里加载

如传递给require()的模块不是一个本地模块,并且不以'/','../''./'开头,那么Node会从当前模块的父目录开始,尝试在它的node_modules文件夹里加载模块。

如果没有找到,那么会到父目录,直到到文件系统的根目录里找。

例如,如果'/home/ry/projects/foo.js'里的文件加载require('bar.js'),那么Node将会按照下面的顺序查找:

  • /home/ry/projects/node_modules/bar.js
  • /home/ry/node_modules/bar.js
  • /home/node_modules/bar.js
  • /node_modules/bar.js

这样允许程序独立,不会产生冲突。

可以请求指定的文件或分布子目录里的模块,在模块名后添加路径后缀。例如,require('example-module/path/to/file')会解决path/to/file相对于example-module的加载位置。路径后缀使用相同语法。

文件夹作为模块

可以把程序和库放到独立的文件夹里,并提供单一的入口指向他们。有三种方法可以将文件夹作为参数传给require()

第一个方法是,在文件夹的根创建一个package.json文件,它指定了main模块。package.json的例子如下:

{ "name" : "some-library",  "main" : "./lib/some-library.js" }

如果这是在./some-library里的文件夹,require('./some-library')将会试着加载./some-library/lib/some-library.js

如果文件夹里没有package.json文件,Node会试着加载index.jsindex.node文件。例如,如果上面的例子里没有package.json文件。那么 require('./some-library')将会试着加载:

  • ./some-library/index.js
  • ./some-library/index.node

缓存

模块第一次加载后会被被缓存。这就是说,每次调用require('foo')都会返回同一个对象,当然,必须每次都要解析到同一个文件。

多次调用require('foo')也许不会导致模块代码多次执行。这是很重要的特性,这样就可以返回"partially done"对象,允许加载过渡性的依赖关系,即使可能会引起环形调用。

如果你希望多次调用一个模块,那么就输出一个函数,然后调用这个函数。

模块换成预警

模块的缓存依赖于解析后的文件名。因此随着调用位置的不同,模块可能解析到不同的文件(例如,从node_modules文件夹加载)。如果解析为不同的文件,require('foo')可能会返回不同的对象。

module 对象

  • {Object}

在每个模块中,变量module是一个代表当前模块的对象的引用。为了方便,module.exports可以通过exports全局模块访问。module不是事实上的全局对象,而是每个模块内部的。

module.exports

  • {Object}

模块系统创建module.exports对象。很多人希望自己的模块是某个类的实例。因此,把将要导出的对象赋值给module.exports。注意,将想要的对象赋值给 exports,只是简单的将它绑定到本地exports变量,这可能并不是你想要的。

例如,假设我们有一个模块叫a.js

var EventEmitter = require('events').EventEmitter;module.exports = new EventEmitter();// Do some work, and after some time emit// the 'ready' event from the module itself.setTimeout(function() {  module.exports.emit('ready');}, 1000);

另一个文件可以写成如下的形式:

var a = require('./a');a.on('ready', function() {  console.log('module a is ready');});

注意:赋给module.exports必须马上执行,并且不能在回调中执行。

x.js:

setTimeout(function() {  module.exports = { a: "hello" };}, 0);

y.js:

var x = require('./x');console.log(x.a);

exports alias

exports变量在引用到module.exports的模块里可用。和其他变量一样,如果你给他赋一个新的值,它不再指向老的值。

为了展示这个特性,假设实现:require():

function require(...) {  // ...  function (module, exports) {    // Your module code here    exports = some_func;        // re-assigns exports, exports is no longer                                // a shortcut, and nothing is exported.    module.exports = some_func; // makes your module export 0  } (module, module.exports);  return module;}

如果你对exportsmodule.exports间的关系感到迷糊,那就只用module.exports就好。

module.require(id)

  • id {String}
  • 返回: {Object} 已经解析模块的module.exports

module.require方法提供了一种像require()一样从最初的模块加载一个模块的方法。

为了能这样做,你必须获得module对象的引用。require()返回module.exports,并且module是一个典型的只能在特定模块作用域内有效的变量,如果要使用它,就必须明确的导出。

module.id

  • {String}

模块的标识符。通常是完全解析的文件名。

module.filename

  • {String}

模块完全解析的文件名。

module.loaded

  • {Boolean}

模块是已经加载完毕,还是在加载中。

module.parent

  • {Module Object}

引入这个模块的模块。

module.children

  • {Array}

由这个模块引入的模块。

其他...

为了获取即将用require()加载的准确文件名,可以使用require.resolve()函数。

综上所述,下面用伪代码的高级算法形式演示了require.resolve的工作流程:

require(X) from module at path Y1. If X is a core module,   a. return the core module   b. STOP2. If X begins with './' or '/' or '../'   a. LOAD_AS_FILE(Y + X)   b. LOAD_AS_DIRECTORY(Y + X)3. LOAD_NODE_MODULES(X, dirname(Y))4. THROW "not found"LOAD_AS_FILE(X)1. If X is a file, load X as JavaScript text.  STOP2. If X.js is a file, load X.js as JavaScript text.  STOP3. If X.json is a file, parse X.json to a JavaScript Object.  STOP4. If X.node is a file, load X.node as binary addon.  STOPLOAD_AS_DIRECTORY(X)1. If X/package.json is a file,   a. Parse X/package.json, and look for "main" field.   b. let M = X + (json main field)   c. LOAD_AS_FILE(M)2. If X/index.js is a file, load X/index.js as JavaScript text.  STOP3. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP4. If X/index.node is a file, load X/index.node as binary addon.  STOPLOAD_NODE_MODULES(X, START)1. let DIRS=NODE_MODULES_PATHS(START)2. for each DIR in DIRS:   a. LOAD_AS_FILE(DIR/X)   b. LOAD_AS_DIRECTORY(DIR/X)NODE_MODULES_PATHS(START)1. let PARTS = path split(START)2. let I = count of PARTS - 13. let DIRS = []4. while I >= 0,   a. if PARTS[I] = "node_modules" CONTINUE   c. DIR = path join(PARTS[0 .. I] + "node_modules")   b. DIRS = DIRS + DIR   c. let I = I - 15. return DIRS

从全局文件夹加载

如果环境变量NODE_PATH设置为冒号分割的绝对路径列表,并且在模块在其他地方没有找到,Node将会搜索这些路径。(注意,在Windows系统中,NODE_PATH用分号分割 )。

另外,Node将会搜索这些路径。

  • 1:$HOME/.node_modules
  • 2:$HOME/.node_libraries
  • 3:$PREFIX/lib/node

$HOME是用户的home文件夹,$PREFIX是Node里配置的node_prefix

这大多是历史原因照成的。强烈建议将所以来的模块放到node_modules文件夹里。这样加载会更快。

访问主模块

当Node运行一个文件时,require.main就会设置为它的module。也就是说你可以通过测试判断文件是否被直接运行。

require.main === module

对于foo.js文件。 如果直接运行node foo.js,返回true,如果通过require('./foo')是间接运行。

因为module提供了filename属性(通常等于__filename),程序的入口点可以通过检查require.main.filename来获得。

附录: 包管理技巧

Node的require()函数语义定义的足够通用,它能支持各种常规目录结构。诸如dpkg,rpmnpm包管理程序,不用修改就可以从Node模块构建本地包。

下面我们介绍一个可行的目录结构:

假设我们有一个/usr/lib/node/<some-package>/<some-version>文件夹,它包含指定版本的包内容。

一个包可以依赖于其他包。为了安装包foo,可能需要安装特定版本的bar包。bar包可能有自己的包依赖,某些条件下,依赖关系可能会发生冲突或形成循环。

因为Node会查找他所加载的模块的realpath(也就是说会解析符号链接),然后按照上文描述的方式在node_modules目录中寻找依赖关系,这种情形跟以下体系结构非常相像:

  • /usr/lib/node/foo/1.2.3/ - foo包,version 1.2.3。
  • /usr/lib/node/bar/4.3.2/ - foo依赖的bar包内容。
  • /usr/lib/node/foo/1.2.3/node_modules/bar- 指向/usr/lib/node/bar/4.3.2/的符号链接。
  • /usr/lib/node/bar/4.3.2/node_modules/*- 指向bar包所依赖的包的符号链接。

因此,即使存在循环依赖或依赖冲突,每个模块还可以获得他所依赖的包得可用版本。

foo包里的代码调用foo ,将会获得符号链接/usr/lib/node/foo/1.2.3/node_modules/bar指向的版本。然后,当bar包中的代码调用 require('queue'),将会获得符号链接/usr/lib/node/bar/4.3.2/node_modules/quux指向的版本。

另外,为了让模块搜索更快些,不要将包直接放在/usr/lib/node目录中,而是将它们放在/usr/lib/node_modules/<name>/<version>目录中。这样在依赖的包找不到的情况下,就不会一直寻找/usr/node_modules目录或/node_modules目录了。基于调用require()的文件所在真实路径,因此包本身可以放在任何位置。

为了让Node模块对Node REPL可用,可能需要将/usr/lib/node_modules文件夹路径添加到环境变量$NODE_PATH。由于模块查找$NODE_PATH文件夹都是相对路径,因此包可以放到任何位置。


稳定性: 2 - 不稳定; 正在讨论未来版本的 API 改进,会尽量减少重大变化。详见后文。

顾名思义,Node.js加密模块允许你使用加密的功能,Node.js加密模块通过使用require('crypto')来访问。

Node.js加密模块提供了HTTP或HTTPS连接过程中封装安全凭证的方法。

Node.js加密模块还提供了OpenSSL的哈希,hmac、加密(cipher)、解密(decipher)、签名(sign)和验证(verify)方法的封装。

crypto.setEngine(engine[, flags])

为某些/所有OpenSSL函数加载并设置引擎(根据参数flags来设置)。

engine可能是id,或者是指向引擎共享库的路径。

flags是可选参数,默认值是ENGINE_METHOD_ALL,它可以是以下一个或多个参数的组合(在constants里定义):

  • ENGINE_METHOD_RSA
  • ENGINE_METHOD_DSA
  • ENGINE_METHOD_DH
  • ENGINE_METHOD_RAND
  • ENGINE_METHOD_ECDH
  • ENGINE_METHOD_ECDSA
  • ENGINE_METHOD_CIPHERS
  • ENGINE_METHOD_DIGESTS
  • ENGINE_METHOD_STORE
  • ENGINE_METHOD_PKEY_METH
  • ENGINE_METHOD_PKEY_ASN1_METH
  • ENGINE_METHOD_ALL
  • ENGINE_METHOD_NONE

crypto.getCiphers()

返回支持的加密算法名数组。

例如:

var ciphers = crypto.getCiphers();console.log(ciphers); // ['AES-128-CBC', 'AES-128-CBC-HMAC-SHA1', ...]

crypto.getHashes()

返回支持的哈希算法名数组。

例如:

var hashes = crypto.getHashes();console.log(hashes); // ['sha', 'sha1', 'sha1WithRSAEncryption', ...]

crypto.createCredentials(details)

稳定性: 0 - 抛弃。用 [tls.createSecureContext][] 替换

根据参数details,创建一个加密凭证对象。参数为字典,key包括:

  • pfx: 字符串或者buffer对象,表示经PFX或PKCS12编码产生的私钥、证书以及CA证书
  • key: 进过 PEM 编码的私钥
  • passphrase: 私钥或pfx的密码
  • cert: PEM编码的证书
  • ca: 字符串或字符串数组,PEM编码的可信任的CA证书。
  • crl: 字符串或字符串数组,PEM编码的CRLs(证书吊销列表Certificate Revocation List)。
  • ciphers: 字符串,使用或者排除的加密算法。参见http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT

如果没有指定'ca',Node.js将会使用下面列表中的CAhttp://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt

crypto.createHash(algorithm)

创建并返回一个哈希对象,使用指定的算法来生成哈希摘要。

参数algorithm取决于平台的OpenSSL版本所支持的算法。例如,'sha1''md5''sha256''sha512'等等。在最近的版本中,openssllist-message-digest-algorithms会显示所有算法。

例如: 这个程序会计算文件的sha1的和。

var filename = process.argv[2];var crypto = require('crypto');var fs = require('fs');var shasum = crypto.createHash('sha1');var s = fs.ReadStream(filename);s.on('data', function(d) {  shasum.update(d);});s.on('end', function() {  var d = shasum.digest('hex');  console.log(d + '  ' + filename);});

类:Hash

Hase用来生成数据的哈希值。

它是可读写的流stream。写入的数据来用计算哈希值。当写入流结束后,使用read()方法来获取计算后的哈希值。也支持旧的updatedigest方法。

通过crypto.createHash返回。

hash.update(data[, input_encoding])

根据data来更新哈希内容,编码方式根据input_encoding来定,有'utf8''ascii''binary'。如果没有传入值,默认编码方式是'binary'。如果 dataBuffer,则input_encoding将会被忽略。

因为它是流式数据,所以可以使用不同的数据调用很多次。

hash.digest([encoding])

计算传入的数据的哈希摘要。

encoding可以是'hex''binary''base64',如果没有指定encoding,将返回buffer。
注意:调用digest()后不能再用hash对象。

crypto.createHmac(algorithm, key)

创建并返回一个hmac对象,用指定的算法和秘钥生成hmac图谱。

它是可读写的流stream。写入的数据来用计算hmac。当写入流结束后,使用read()方法来获取计算后的值。也支持旧的updatedigest方法。

参数algorithm取决于平台上OpenSSL版本所支持的算法,参见前面的createHash。key是hmac算法中用的key。

类:Hmac

用来创建hmac加密图谱。

通过crypto.createHmac返回。

hmac.update(data)

根据data更新hmac对象。因为它是流式数据,所以可以使用新数据调用多次。

hmac.digest([encoding])

计算传入数据的hmac值。encoding可以是'hex''binary''base64',如果没有指定encoding,将返回buffer。

注意:调用digest()后不能再用hmac对象。

crypto.createCipher(algorithm, password)

使用传入的算法和秘钥来生成并返回加密对象。

algorithm取决于OpenSSL,例如'aes192'等。最近发布的版本中,openssl list-cipher-algorithms将会展示可用的加密算法。password用来派生key 和IV,它必须是一个'binary'编码的字符串或者一个buffer

它是可读写的stream流。写入的数据来用计算hmac。当写入流结束后,使用read()方法来获取计算后的值。也支持老的updatedigest方法。

注意,OpenSSL函数EVP_BytesToKey摘要算法如果是一次迭代(one iteration),无需盐值(no salt)的MD5时,createCipher为它派生秘钥。缺少盐值使得字典攻击,相同的密码总是生成相同的key,低迭代次数和非加密的哈希算法,使得密码测试非常迅速。

OpenSSL推荐使用pbkdf2来替换EVP_BytesToKey,推荐使用crypto.pbkdf2来派生key和iv ,推荐使用createCipheriv()来创建加密流。

crypto.createCipheriv(algorithm, key, iv)

创建并返回一个加密对象,用指定的算法,key和iv。

algorithm参数和createCipher()一致。key在算法中用到。iv是一个initialization vector.

keyiv必须是'binary'的编码字符串或buffers.

类: Cipher

加密数据的类。.

通过crypto.createCiphercrypto.createCipheriv返回。

它是可读写的stream流。写入的数据来用计算hmac。当写入流结束后,使用read()方法来获取计算后的值。也支持旧的updatedigest方法。

cipher.update(data[, input_encoding][, output_encoding])

根据data来更新哈希内容,编码方式根据input_encoding来定,有'utf8''ascii'或者'binary'。如果没有传入值,默认编码方式是'binary'。如果dataBufferinput_encoding将会被忽略。

output_encoding指定了输出的加密数据的编码格式,它可用是'binary''base64''hex'。如果没有提供编码,将返回buffer。

返回加密后的内容,因为它是流式数据,所以可以使用不同的数据调用很多次。

cipher.final([output_encoding])

返回加密后的内容,编码方式是由output_encoding指定,可以是'binary''base64''hex'。如果没有传入值,将返回buffer。

注意:cipher对象不能在final()方法之后调用。

cipher.setAutoPadding(auto_padding=true)

你可以禁用输入数据自动填充到块大小的功能。如果auto_padding是false, 那么输入数据的长度必须是加密器块大小的整倍数,否则final会失败。这对非标准的填充很有用,例如使用0x0而不是PKCS的填充。这个函数必须在cipher.final之前调用。

cipher.getAuthTag()

加密认证模式(目前支持:GCM),这个方法返回经过计算的认证标志Buffer。必须使用final方法完全加密后调用。

cipher.setAAD(buffer)

加密认证模式(目前支持:GCM),这个方法设置附加认证数据( AAD )。

crypto.createDecipher(algorithm, password)

根据传入的算法和密钥,创建并返回一个解密对象。这是createCipher()的镜像。

crypto.createDecipheriv(algorithm, key, iv)

根据传入的算法,密钥和iv,创建并返回一个解密对象。这是createCipheriv()的镜像。

类:Decipher

解密数据类。

通过crypto.createDeciphercrypto.createDecipheriv返回。

解密对象是可读写的streams流。用写入的加密数据生成可读的纯文本数据。也支持老的updatedigest方法。

decipher.update(data[, input_encoding][, output_encoding])

使用参数data更新需要解密的内容,其编码方式是'binary''base64''hex'。如果没有指定编码方式,则把data当成buffer对象。

如果dataBuffer,则忽略input_encoding参数。

参数output_decoding指定返回文本的格式,是'binary''ascii''utf8'之一。如果没有提供编码格式,则返回buffer。

decipher.final([output_encoding])

返回剩余的解密过的内容,参数output_encoding'binary''ascii''utf8',如果没有指定编码方式,返回buffer。

注意:decipher对象不能在final()方法之后使用。

decipher.setAutoPadding(auto_padding=true)

如果加密的数据是非标准块,可以禁止其自动填充,防止decipher.final检查并移除。仅在输入数据长度是加密块长度的整数倍的时才有效。你必须在 decipher.update前调用。

decipher.setAuthTag(buffer)

对于加密认证模式(目前支持:GCM),必须用这个方法来传递接收到的认证标志。如果没有提供标志,或者密文被篡改,将会抛出final标志,认证失败,密文会被抛弃。

decipher.setAAD(buffer)

对于加密认证模式(目前支持:GCM),用这个方法设置附加认证数据( AAD )。

crypto.createSign(algorithm)

根据传入的算法创建并返回一个签名数据。 OpenSSL的最近版本里,openssl list-public-key-algorithms会列出所有算法,比如'RSA-SHA256'

类:Sign

生成数字签名的类。

通过crypto.createSign返回。

签名对象是可读写的streams流。可写数据用来生成签名。当所有的数据写完,sign签名方法会返回签名。也支持老的updatedigest方法。

sign.update(data)

用参数data来更新签名对象。因为是流式数据,它可以被多次调用。

sign.sign(private_key[, output_format])

根据传送给sign的数据来计算电子签名。

private_key可以是一个对象或者字符串。如果是字符串,将会被当做没有密码的key。

private_key:

  • key: 包含 PEM 编码的私钥
  • passphrase: 私钥的密码

返回值output_format包含数字签名, 格式是'binary''hex''base64'之一。如果没有指定encoding,将返回buffer。

注意:sign对象不能在sign()方法之后调用。

crypto.createVerify(algorithm)

根据传入的算法,创建并返回验证对象。是签名对象(signing object)的镜像。

类: Verify

用来验证签名的类。

通过crypto.createVerify返回。

是可写streams流。可写数据用来验证签名。一旦所有数据写完后,如签名正确verify方法会返回true

也支持老的update方法。

verifier.update(data)

用参数data来更新验证对象。因为是流式数据,它可以被多次调用。

verifier.verify(object, signature[, signature_format])

使用objectsignature验证签名数据。参数object是包含了PEM编码对象的字符串,它可以是RSA公钥,DSA公钥,或X.509证书。signature是之前计算出来的数字签名。signature_format可以是'binary''hex''base64'之一,如果没有指定编码方式 ,则默认是buffer对象。

根据数据和公钥验证签名有效性,来返回true或false。

注意:verifier对象不能在verify()方法之后调用。

crypto.createDiffieHellman(prime_length[, generator])

创建一个Diffie-Hellman密钥交换(Diffie-Hellman key exchange)对象,并根据给定的位长度生成一个质数。如果没有指定参数generator,默认为2

crypto.createDiffieHellman(prime[, prime_encoding][, generator][, generator_encoding])

使用传入的primegenerator创建Diffie-Hellman秘钥交互对象。

generator可以是数字,字符串或Buffer。

如果没有指定generator,使用2.

prime_encodinggenerator_encoding可以是'binary''hex''base64'

如果没有指定prime_encoding, 则Buffer为prime

如果没有指定generator_encoding ,则Buffer为generator

类:DiffieHellman

创建Diffie-Hellman秘钥交换的类。

通过crypto.createDiffieHellman返回。

diffieHellman.verifyError

在初始化的时候,如果有警告或错误,将会反应到这。它是以下值(定义在constants模块):

  • DH_CHECK_P_NOT_SAFE_PRIME
  • DH_CHECK_P_NOT_PRIME
  • DH_UNABLE_TO_CHECK_GENERATOR
  • DH_NOT_SUITABLE_GENERATOR

diffieHellman.generateKeys([encoding])

生成秘钥和公钥,并返回指定格式的公钥。这个值必须传给其他部分。编码方式:'binary''hex''base64'。如果没有指定编码方式,将返回buffer。

diffieHellman.computeSecret(other_public_key[, input_encoding][, output_encoding])

使用other_public_key作为第三方公钥来计算并返回共享秘密(shared secret)。秘钥用input_encoding编码。编码方式为:'binary''hex''base64'。如果没有指定编码方式 ,默认为buffer。

如果没有指定返回编码方式,将返回buffer。

diffieHellman.getPrime([encoding])

用参数encoding指明的编码方式返回Diffie-Hellman质数,编码方式为: 'binary''hex''base64'。如果没有指定编码方式,将返回buffer。

diffieHellman.getGenerator([encoding])

用参数encoding指明的编码方式返回Diffie-Hellman生成器,编码方式为: 'binary''hex''base64'。如果没有指定编码方式 ,将返回buffer。

diffieHellman.getPublicKey([encoding])

用参数encoding指明的编码方式返回Diffie-Hellman公钥,编码方式为: 'binary''hex', 或'base64'。如果没有指定编码方式 ,将返回buffer。

diffieHellman.getPrivateKey([encoding])

用参数encoding指明的编码方式返回Diffie-Hellman私钥,编码方式为: 'binary''hex''base64'。如果没有指定编码方式 ,将返回buffer。

diffieHellman.setPublicKey(public_key[, encoding])

设置Diffie-Hellman的公钥,编码方式为: 'binary''hex''base64',如果没有指定编码方式 ,默认为buffer。

diffieHellman.setPrivateKey(private_key[, encoding])

设置Diffie-Hellman的私钥,编码方式为: 'binary''hex''base64',如果没有指定编码方式 ,默认为buffer。

crypto.getDiffieHellman(group_name)

创建一个预定义的Diffie-Hellman秘钥交换对象。支持的组: 'modp1''modp2''modp5'(定义于RFC 2412),并且'modp14''modp15''modp16''modp17''modp18'(定义于RFC 3526)。返回对象模仿了上述创建的crypto.createDiffieHellman()对象,但是不允许修改秘钥交换(例如,diffieHellman.setPublicKey())。使用这套流程的好处是,双方不需要生成或交换组组余数,节省了计算和通讯时间。

例如 (获取一个共享秘密):

var crypto = require('crypto');var alice = crypto.getDiffieHellman('modp5');var bob = crypto.getDiffieHellman('modp5');alice.generateKeys();bob.generateKeys();var alice_secret = alice.computeSecret(bob.getPublicKey(), null, 'hex');var bob_secret = bob.computeSecret(alice.getPublicKey(), null, 'hex');/* alice_secret and bob_secret should be the same */console.log(alice_secret == bob_secret);

crypto.createECDH(curve_name)

使用传入的参数curve_name,创建一个Elliptic Curve (EC) Diffie-Hellman秘钥交换对象。

类:ECDH

这个类用来创建EC Diffie-Hellman秘钥交换。

通过crypto.createECDH返回。

ECDH.generateKeys([encoding[, format]])

生成EC Diffie-Hellman的秘钥和公钥,并返回指定格式和编码的公钥,它会传递给第三方。

参数format'compressed'、 'uncompressed''hybrid'。如果没有指定,将返回'uncompressed'格式.

参数encoding'binary''hex''base64'。如果没有指定编码方式,将返回buffer。

ECDH.computeSecret(other_public_key[, input_encoding][, output_encoding])

other_public_key作为第三方公钥计算共享秘密,并返回。秘钥会以input_encoding来解读。编码是:'binary''hex''base64'。如果没有指定编码方式,默认为buffer。

如果没有指定编码方式,将返回buffer。

ECDH.getPublicKey([encoding[, format]])

用参数encoding指明的编码方式返回EC Diffie-Hellman公钥,编码方式为: 'compressed''uncompressed''hybrid'。如果没有指定编码方式 ,将返回'uncompressed'

编码是:'binary''hex''base64'。如果没有指定编码方式 ,默认为buffer。

ECDH.getPrivateKey([encoding])

用参数encoding指明的编码方式返回EC Diffie-Hellman私钥,编码是:'binary''hex''base64'。如果没有指定编码方式 ,默认为buffer。

ECDH.setPublicKey(public_key[, encoding])

设置EC Diffie-Hellman的公钥,编码方式为: 'binary''hex''base64',如果没有指定编码方式,默认为buffer。

ECDH.setPrivateKey(private_key[, encoding])

设置EC Diffie-Hellman的私钥,编码方式为: 'binary''hex''base64',如果没有指定编码方式,默认为buffer。

例如 (包含一个共享秘密):

var crypto = require('crypto');var alice = crypto.createECDH('secp256k1');var bob = crypto.createECDH('secp256k1');alice.generateKeys();bob.generateKeys();var alice_secret = alice.computeSecret(bob.getPublicKey(), null, 'hex');var bob_secret = bob.computeSecret(alice.getPublicKey(), null, 'hex');/* alice_secret and bob_secret should be the same */console.log(alice_secret == bob_secret);

crypto.pbkdf2(password, salt, iterations, keylen[, digest], callback)

异步PBKDF2提供了一个伪随机函数HMAC-SHA1,根据给定密码的长度,salt和iterations来得出一个密钥。回调函数得到两个参数 (err, derivedKey)。

例如:

crypto.pbkdf2('secret', 'salt', 4096, 512, 'sha256', function(err, key) {  if (err)    throw err;  console.log(key.toString('hex'));  // 'c5e478d...1469e50'});

crypto.getHashes()里有支持的摘要函数列表。

crypto.pbkdf2Sync(password, salt, iterations, keylen[, digest])

异步PBKDF2函数, 返回derivedKey或抛出错误。

crypto.randomBytes(size[, callback])

生成一个密码强度随机的数据:

// asynccrypto.randomBytes(256, function(ex, buf) {  if (ex) throw ex;  console.log('Have %d bytes of random data: %s', buf.length, buf);});// synctry {  var buf = crypto.randomBytes(256);  console.log('Have %d bytes of random data: %s', buf.length, buf);} catch (ex) {  // handle error  // most likely, entropy sources are drained}

注意:如果没有足够积累的熵来生成随机强度的密码,将会抛出错误,或调用回调函数返回错误。换句话说,没有回调函数的crypto.randomBytes不会阻塞,即使耗尽所有的熵。

crypto.pseudoRandomBytes(size[, callback])

生成非密码学强度的伪随机数据。如果数据足够长会返回一个唯一数据,但是这个数可能是可以预期的。因此,当不可预期很重要的时候,不要用这个函数。例如,在生成加密的秘钥时。

用法和crypto.randomBytes相同。

类: Certificate

这个类和签过名的公钥打交道。最重要的场景是处理<keygen>元素,http://www.openssl.org/docs/apps/spkac.html

通过crypto.Certificate返回.

Certificate.verifySpkac(spkac)

根据SPKAC返回true或false。

Certificate.exportChallenge(spkac)

根据提供的SPKAC,返回加密的公钥。

Certificate.exportPublicKey(spkac)

输出和SPKAC关联的编码challenge。

crypto.publicEncrypt(public_key, buffer)

使用public_key加密buffer。目前仅支持RSA。

public_key可以是对象或字符串。如果public_key是一个字符串,将会当做没有密码的key,并会用RSA_PKCS1_OAEP_PADDING

public_key:

  • key: 包含有PEM编码的私钥。
  • padding: 填充值,如下
    • constants.RSA_NO_PADDING
    • constants.RSA_PKCS1_PADDING
    • constants.RSA_PKCS1_OAEP_PADDING

注意:所有的填充值定义在constants模块.

crypto.privateDecrypt(private_key, buffer)

使用private_key来解密buffer.

private_key:

  • key: 包含有 PEM 编码的私钥
  • passphrase: 私钥的密码
  • padding: 填充值,如下:
    • constants.RSA_NO_PADDING
    • constants.RSA_PKCS1_PADDING
    • constants.RSA_PKCS1_OAEP_PADDING

注意:所有的填充值定义于constants模块.

crypto.DEFAULT_ENCODING

函数所用的编码方式可以是字符串或buffer ,默认值是'buffer'。这是为了加密模块兼容默认'binary'为编码方式的遗留程序。

注意:新程序希望用buffer对象,所以这是暂时手段。

Recent API Changes

在统一的流API概念出现前,在引入Buffer对象来处理二进制数据之前,Crypto模块就已经添加到Node。

因此,流相关的类里没有其他的Node类里的典型方法,并且很多方法接收并返回二级制编码的字符串,而不是Buffers。在最近的版本中,这些函数改成默认使用 Buffers。

对于一些场景来说这是重大变化。

例如,如果你使用默认参数给签名类,将结果返回给认证类,中间没有验证数据,程序会正常工作。之前你会得到二进制编码的字符串,并传递给验证类,现在则是 Buffer。

如果你之前使用的字符串数据在Buffers对象不能正常工作(比如,连接数据,并存储在数据库里 )。或者你传递了二进制字符串给加密函数,但是没有指定编码方式,现在就需要提供编码参数。如果想切换回原来的风格,将crypto.DEFAULT_ENCODING设置为'binary'。注意,新的程序希望是buffers,所以之前的方法只能作为临时的办法。


稳定性: 2 - 不稳定

流用于处理Node.js中的流数据的抽象接口,在Node里被不同的对象实现。例如,对HTTP服务器的请求是流,process.stdout 是流。

流是可读的,可写的,或者是可读写的,所有的流是EventEmitter的实例。

Node.js访问流模块的方法如下所示:

const stream = require('stream');

你可以通过require('stream')加载Stream基类。其中包括了Readable流、Writable流、Duplex流和Transform流的基类。

本文将分为3个部分进行介绍。

第一个部分解释了在你的程序中使用流时候需要了解的内容。如果你不用实现流式API,可以只看这个部分。

如果你想实现你自己的流,第二个部分解释了这部分API。这些API让你的实现更加简单。

第三个部分深入的解释了流是如何工作的,包括一些内部机制和函数,这些内容不要改动,除非你明确知道你要做什么。

面向流消费者的API

流可以是可读(Readable),可写(Writable),或者是可读可写的(Duplex,双工)。

所有的流都是事件分发器(EventEmitters),但是也有自己的方法和属性,这取决于他它们是可读(Readable),可写(Writable),或者兼具两者(Duplex,双工)的。

如果流是可读写的,则它实现了下面的所有方法和事件。因此,这个部分API完全阐述了DuplexTransform流,即便他们的实现有所不同。

没有必要为了消费流而在你的程序里实现流的接口。如果你正在你的程序里实现流接口,请同时参考下面的流实现程序API

基本所有的Node程序,无论多简单,都会使用到流。以下是一个使用流的例子:

javascriptvar http = require('http');var server = http.createServer(function (req, res) {  // req is an http.IncomingMessage, which is 可读流(Readable stream)  // res is an http.ServerResponse, which is a Writable Stream  var body = '';  // we want to get the data as utf8 strings  // If you don't set an encoding, then you'll get Buffer objects  req.setEncoding('utf8');  // 可读流(Readable stream) emit 'data' 事件 once a 监听器(listener) is added  req.on('data', function (chunk) {    body += chunk;  });  // the end 事件 tells you that you have entire body  req.on('end', function () {    try {      var data = JSON.parse(body);    } catch (er) {      // uh oh!  bad json!      res.statusCode = 400;      return res.end('error: ' + er.message);    }    // write back something interesting to the user:    res.write(typeof data);    res.end();  });});server.listen(1337);// $ curl localhost:1337 -d '{}'// object// $ curl localhost:1337 -d '"foo"'// string// $ curl localhost:1337 -d 'not json'// error: Unexpected token o

类: stream.Readable

可读流(Readable stream)接口是对你正在读取的数据的来源的抽象。换句话说,数据来来自

可读流(Readable stream)不会分发数据,直到你表明准备就绪。

可读流(Readable stream) 有2种模式: 流动模式(flowing mode)暂停模式(paused mode)。流动模式(flowing mode)时,尽快的从底层系统读取数据并提供给你的程序。暂停模式(paused mode)时,你必须明确的调用stream.read()来读取数据。暂停模式(paused mode)是默认模式。

注意: 如果没有绑定数据处理函数,并且没有pipe()目标,流会切换到流动模式(flowing mode),并且数据会丢失。

可以通过下面几个方法,将流切换到流动模式(flowing mode)。

  • 添加一个['data' 事件][]事件处理器来监听数据.
  • 调用resume()方法来明确的开启数据流。
  • 调用pipe()方法来发送数据给Writable.

可以通过以下方法来切换到暂停模式(paused mode):

  • 如果没有“导流(pipe)”目标,调用pause()方法.
  • 如果有“导流(pipe)”目标,移除所有的['data'事件][]处理函数,调用unpipe()方法移除所有的“导流(pipe)”目标。

注意:为了向后兼容考虑, 移除'data'事件监听器并不会自动暂停流。同样的,当有导流目标时,调用pause()并不能保证流在那些目标排空后,请求更多数据时保持暂停状态。

可读流(Readable stream)例子包括:

事件: 'readable'

当一个数据块可以从流中读出,将会触发'readable'事件.`

某些情况下, 如果没有准备好,监听一个'readable'事件将会导致一些数据从底层系统读取到内部缓存。

javascriptvar readble = getReadableStreamSomehow();readable.on('readable', function() {  // there is some data to read now});

一旦内部缓存排空,一旦有更多数据将会再次触发readable事件。

事件: 'data'

  • chunk {Buffer | String} 数据块

绑定一个data事件的监听器(listener)到一个未明确暂停的流,会将流切换到流动模式。数据会尽额能的传递。

如果你像尽快的从流中获取数据,以下的方法是最快的:

javascriptvar readable = getReadableStreamSomehow();readable.on('data', function(chunk) {  console.log('got %d bytes of data', chunk.length);});

事件: 'end'

如果没有更多的可读数据,将会触发这个事件。

注意:只有数据已经被完全消费,end事件才会触发。可以通过切换到流动模式(flowing mode)来实现,或者通过调用重复调用read()获取数据,直到结束。

javascript    var readable = getReadableStreamSomehow();    readable.on('data', function(chunk) {        console.log('got %d bytes of data', chunk.length);    });    readable.on('end', function() {        console.log('there will be no more data.');    });  

事件: 'close'

当底层资源(例如源头的文件描述符)关闭时触发。并不是所有流都会触发这个事件。

事件: 'error'

  • {Error Object}

当接收数据时发生错误触发。

readable.read([size])

  • size {Number} 可选参数, 需要读入的数据量
  • 返回 {String | Buffer | null}

read()方法从内部缓存中拉取数据。如果没有可用数据,将会返回null

如果传了size参数,将会返回相当字节的数据。如果size不可用,将会返回null

如果你没有指定size参数。将会返回内部缓存的所有数据。

这个方法仅能再暂停模式(paused mode)里调用。 流动模式(flowing mode)下这个方法会被自动调用直到内存缓存排空。

javascriptvar readable = getReadableStreamSomehow();readable.on('readable', function() {  var chunk;  while (null !== (chunk = readable.read())) {    console.log('got %d bytes of data', chunk.length);  }});

如果这个方法返回一个数据块, 它同时也会触发['data'事件][].

readable.setEncoding(encoding)

  • encoding {String} 要使用的编码.
  • 返回:this

调用此函数会使得流返回指定编码的字符串,而不是Buffer对象。例如,如果你调用readable.setEncoding('utf8'),输出数据将会是UTF-8编码,并且返回字符串。如果你调用readable.setEncoding('hex'),将会返回2进制编码的数据。

该方法能正确处理多字节字符。如果不想这么做,仅简单的直接拉取缓存并调buf.toString(encoding),可能会导致字节错位。因此,如果你想以字符串读取数据,请使用下述的方法:

javascriptvar readable = getReadableStreamSomehow();readable.setEncoding('utf8');readable.on('data', function(chunk) {  assert.equal(typeof chunk, 'string');  console.log('got %d characters of string data', chunk.length);});

readable.resume()

  • 返回:this

这个方法让可读流(Readable stream)继续触发data事件.

这个方法会将流切换到流动模式(flowing mode)。 如果你不想从流中消费数据,而想得到end事件,可以调用readable.resume()来打开数据流,如下所示:

javascriptvar readable = getReadableStreamSomehow();readable.resume();readable.on('end', function(chunk) {  console.log('got to the end, but did not read anything');});

readable.pause()

  • 返回:this

这个方法会使得流动模式(flowing mode)的流停止触发data事件,切换到流动模式(flowing mode)。并让后续可用数据留在内部缓冲区中。

javascriptvar readable = getReadableStreamSomehow();readable.on('data', function(chunk) {  console.log('got %d bytes of data', chunk.length);  readable.pause();  console.log('there will be no more data for 1 second');  setTimeout(function() {    console.log('now data will start flowing again');    readable.resume();  }, 1000);});

readable.isPaused()

  • 返回:Boolean

这个方法返回readable是否被客户端代码明确的暂停(调用readable.pause())。

var readable = new stream.Readablereadable.isPaused() // === falsereadable.pause()readable.isPaused() // === truereadable.resume()readable.isPaused() // === false

readable.pipe(destination[, options])

  • destination {Writable Stream} 写入数据的目标
  • options {Object} 导流(pipe)选项
    • end {Boolean} 读取到结束符时,结束写入者。默认 = true

这个方法从可读流(Readable stream)拉取所有数据,并将数据写入到提供的目标中。自动管理流量,这样目标不会快速的可读流(Readable stream)淹没。

可以导流到多个目标。

javascriptvar readable = getReadableStreamSomehow();var writable = fs.createWriteStream('file.txt');// All the data from readable goes into 'file.txt'readable.pipe(writable);

这个函数返回目标流, 因此你可以建立导流链:

javascriptvar r = fs.createReadStream('file.txt');var z = zlib.createGzip();var w = fs.createWriteStream('file.txt.gz');r.pipe(z).pipe(w);

例如:模拟Unix的cat命令:

javascriptprocess.stdin.pipe(process.stdout);

默认情况下,当源数据流触发end的时候调用end(),所以destination不可再写。传{ end:false }作为options,可以保持目标流打开状态。

这会让writer保持打开状态,可以在最后写入"Goodbye":

javascriptreader.pipe(writer, { end: false });reader.on('end', function() {  writer.end('Goodbye
');});

注意:process.stderrprocess.stdout直到进程结束才会关闭,无论是否指定它们。

readable.unpipe([destination])

  • destination {Writable Stream} 可选,指定解除导流的流

这个方法会解除之前调用pipe() 设置的钩子(pipe())。

如果没有指定destination,则所有的导流(pipe)都会被移除。

如果指定了destination,但是没有建立如果没有指定destination,则什么事情都不会发生。

javascriptvar readable = getReadableStreamSomehow();var writable = fs.createWriteStream('file.txt');// All the data from readable goes into 'file.txt',// but only for the first secondreadable.pipe(writable);setTimeout(function() {  console.log('stop writing to file.txt');  readable.unpipe(writable);  console.log('manually close the file stream');  writable.end();}, 1000);

readable.unshift(chunk)

  • chunk {Buffer | String} 数据块插入到读队列中

这个方法很有用,当一个流正被一个解析器消费,解析器可能需要将某些刚拉取出的数据“逆消费”,返回到原来的源,以便流能将它传递给其它消费者。

如果你在程序中必须经常调用stream.unshift(chunk) ,那你可以考虑实现Transform来替换(参见下文API for Stream Implementors)。

javascript// Pull off a header delimited by 

// use unshift() if we get too much// Call the callback with (error, header, stream)var StringDecoder = require('string_decoder').StringDecoder;function parseHeader(stream, callback) {  stream.on('error', callback);  stream.on('readable', onReadable);  var decoder = new StringDecoder('utf8');  var header = '';  function onReadable() {    var chunk;    while (null !== (chunk = stream.read())) {      var str = decoder.write(chunk);      if (str.match(/

/)) {        // found the header boundary        var split = str.split(/

/);        header += split.shift();        var remaining = split.join('

');        var buf = new Buffer(remaining, 'utf8');        if (buf.length)          stream.unshift(buf);        stream.removeListener('error', callback);        stream.removeListener('readable', onReadable);        // now the body of the message can be read from the stream.        callback(null, header, stream);      } else {        // still reading the header.        header += str;      }    }  }}

readable.wrap(stream)

  • stream {Stream} 一个旧式的可读流(Readable stream)

v0.10版本之前的Node流并未实现现在所有流的API(更多信息详见下文“兼容性”部分)。

如果你使用的是旧的Node库,它触发'data'事件,并拥有仅做查询用的pause()方法,那么你能使用wrap()方法来创建一个Readable流来使用旧版本的流,作为数据源。

你应该很少需要用到这个函数,但它会留下方便和旧版本的Node程序和库交互。

例如:

javascriptvar OldReader = require('./old-api-module.js').OldReader;var oreader = new OldReader;var Readable = require('stream').Readable;var myReader = new Readable().wrap(oreader);myReader.on('readable', function() {  myReader.read(); // etc.});

类: stream.Writable

可写流(Writable stream )接口是你正把数据写到一个目标的抽象。

可写流(Writable stream )的例子包括:

writable.write(chunk[, encoding][, callback])

  • chunk {String | Buffer} 准备写的数据
  • encoding {String} 编码方式(如果chunk 是字符串)
  • callback {Function} 数据块写入后的回调
  • 返回: {Boolean} 如果数据已被全部处理返回true

这个方法向底层系统写入数据,并在数据处理完毕后调用所给的回调。

返回值表示你是否应该继续立即写入。如果数据要缓存在内部,将会返回false。否则返回true

返回值仅供参考。即使返回false,你也可能继续写。但是写会缓存在内存里,所以不要做的太过分。最好的办法是等待drain事件后,再写入数据。

事件: 'drain'

如果调用writable.write(chunk)返回 false,drain事件会告诉你什么时候将更多的数据写入到流中。

javascript// Write the data to the supplied 可写流(Writable stream ) 1MM times.// Be attentive to back-pressure.function writeOneMillionTimes(writer, data, encoding, callback) {  var i = 1000000;  write();  function write() {    var ok = true;    do {      i -= 1;      if (i === 0) {        // last time!        writer.write(data, encoding, callback);      } else {        // see if we should continue, or wait        // don't pass the callback, because we're not done yet.        ok = writer.write(data, encoding);      }    } while (i > 0 && ok);    if (i > 0) {      // had to stop early!      // write some more once it drains      writer.once('drain', write);    }  }}

writable.cork()

强制缓存所有写入。

调用.uncork().end()后,会把缓存数据写入。

writable.uncork()

写入所有.cork()调用之后缓存的数据。

writable.setDefaultEncoding(encoding)

  • encoding {String} 新的默认编码
  • 返回:Boolean

给写数据流设置默认编码方式,如编码有效,则返回true ,否则返回false

writable.end([chunk][, encoding][, callback])

  • chunk {String | Buffer} 可选,要写入的数据
  • encoding {String} 编码方式(如果chunk是字符串)
  • callback {Function} 可选, stream结束时的回调函数

当没有更多的数据写入的时候调用这个方法。如果给出,回调会被用作finish事件的监听器。

调用end()后调用write()会产生错误。

javascript// write 'hello, ' and then end with 'world!'var file = fs.createWriteStream('example.txt');file.write('hello, ');file.end('world!');// writing more now is not allowed!

事件: 'finish'

调用end()方法后,并且所有的数据已经写入到底层系统,将会触发这个事件。

javascriptvar writer = getWritableStreamSomehow();for (var i = 0; i < 100; i ++) {  writer.write('hello, #' + i + '!
');}writer.end('this is the end
');writer.on('finish', function() {  console.error('all writes are now complete.');});

事件: 'pipe'

  • src {Readable Stream} 是导流(pipe)到可写流的源流。

无论何时在可写流(Writable stream )上调用pipe()方法,都会触发'pipe'事件,添加这个流到目标。

javascriptvar writer = getWritableStreamSomehow();var reader = getReadableStreamSomehow();writer.on('pipe', function(src) {  console.error('something is piping into the writer');  assert.equal(src, reader);});reader.pipe(writer);

事件: 'unpipe'

  • src {Readable Stream}未写入此可写的源流。

无论何时在可写流(Writable stream )上调用unpipe()方法,都会触发'unpipe'事件,将这个流从目标上移除。

javascriptvar writer = getWritableStreamSomehow();var reader = getReadableStreamSomehow();writer.on('unpipe', function(src) {  console.error('something has stopped piping into the writer');  assert.equal(src, reader);});reader.pipe(writer);reader.unpipe(writer);

事件: 'error'

  • {Error object}

写或导流(pipe)数据时,如果有错误会触发。

类: stream.Duplex

双工流(Duplex streams)是同时实现了ReadableWritable 接口。用法详见下文。

双工流(Duplex streams) 的例子包括:

类: stream.Transform

转换流(Transform streams)是双工Duplex流,它的输出是从输入计算得来。 它实现了ReadableWritable接口. 用法详见下文.

转换流(Transform streams)的例子包括:

流实现程序API

无论实现什么形式的流,模式都是一样的:

  1. 在你的子类中扩展适合的父类。 (util.inherits方法很有帮助)
  2. 在你的构造函数中调用父类的构造函数,以确保内部的机制初始化正确。
  3. 实现一个或多个方法,如下所列。

所扩展的类和要实现的方法取决于你要编写的流类。

Use-case

Class

方法(s) to implement

Reading only

[Readable](#stream_class_stream_readable_1)

[_read][]

Writing only

[Writable](#stream_class_stream_writable_1)

[_write][]

Reading and writing

[Duplex](#stream_class_stream_duplex_1)

[_read][], [_write][]

Operate on written data, then read the result

[Transform](#stream_class_stream_transform_1)

_transform, _flush

在你的代码里,千万不要调用流实现程序API里的方法。否则可能会引起消费流的程序副作用。

类: stream.Readable

stream.Readable是一个可被扩充的、实现了底层_read(size)方法的抽象类。

参照之前的流实现程序API查看如何在你的程序里消费流。以下内容解释了在你的程序里如何实现可读流(Readable stream)。

Example: 计数流

这是可读流(Readable stream)的基础例子,它将从1至1,000,000递增地触发数字,然后结束:

javascriptvar Readable = require('stream').Readable;var util = require('util');util.inherits(Counter, Readable);function Counter(opt) {  Readable.call(this, opt);  this._max = 1000000;  this._index = 1;}Counter.prototype._read = function() {  var i = this._index++;  if (i > this._max)    this.push(null);  else {    var str = '' + i;    var buf = new Buffer(str, 'ascii');    this.push(buf);  }};

Example: 简单协议 v1 (初始版)

和之前描述的parseHeader函数类似,但它被实现为自定义流。注意这个实现不会将输入数据转换为字符串。

实际上,更好的办法是将他实现为Transform流,使用下面的实现方法会更好:

javascript// A parser for a simple data protocol.// "header" is a JSON object, followed by 2 
 characters, and// then a message body.//// 注意: This can be done more simply as a Transform stream!// Using Readable directly for this is sub-optimal.  See the// alternative example below under Transform section.var Readable = require('stream').Readable;var util = require('util');util.inherits(SimpleProtocol, Readable);  function SimpleProtocol(source, options) {  if (!(this instanceof SimpleProtocol))    return new SimpleProtocol(source, options);  Readable.call(this, options;  this._inBody = false;  this._sawFirstCr = false;  // source is 可读流(Readable stream), such as a socket or file  this._source = source;  var self = this;  source.on('end', function() {    self.push(null);  });  // give it a kick whenever the source is readable  // read(0) will not consume any bytes  source.on('readable', function() {    self.read(0);  });  this._rawHeader = [];  this.header = null;}SimpleProtocol.prototype._read = function(n) {  if (!this._inBody) {    var chunk = this._source.read();    // if the source doesn't have data, we don't have data yet.    if (chunk === null)      return this.push('');    // check if the chunk has a 

    var split = -1;    for (var i = 0; i < chunk.length; i++) {      if (chunk[i] === 10) { // '
'        if (this._sawFirstCr) {          split = i;          break;        } else {          this._sawFirstCr = true;        }      } else {        this._sawFirstCr = false;      }    }    if (split === -1) {      // still waiting for the 

      // stash the chunk, and try again.      this._rawHeader.push(chunk);      this.push('');    } else {      this._inBody = true;      var h = chunk.slice(0, split);      this._rawHeader.push(h);      var header = Buffer.concat(this._rawHeader).toString();      try {        this.header = JSON.parse(header);      } catch (er) {        this.emit('error', new Error('invalid simple protocol data'));        return;      }      // now, because we got some extra data, unshift the rest      // back into the 读取队列 so that our consumer will see it.      var b = chunk.slice(split);      this.unshift(b);      // and let them know that we are done parsing the header.      this.emit('header', this.header);    }  } else {    // from there on, just provide the data to our consumer.    // careful not to push(null), since that would indicate EOF.    var chunk = this._source.read();    if (chunk) this.push(chunk);  }};// Usage:// var parser = new SimpleProtocol(source);// Now parser is 可读流(Readable stream) that will emit 'header'// with the parsed header data.

new stream.Readable([options])

  • options{Object}
    • highWaterMark{Number} 停止从底层资源读取数据前,存储在内部缓存的最大字节数;默认=16kb, objectMode流是16.
    • encoding{String} 若指定,则Buffer会被解码成所给编码的字符串,默认为null。
    • objectMode{Boolean} 该流是否为对象的流。意思是说stream.read(n)返回一个单独的值,而不是大小为n的Buffer。

Readable的扩展类中,确保调用了Readable的构造函数,这样才能正确初始化。

readable._read(size)

  • size{Number} 异步读取的字节数

注意:实现这个函数,但不要直接调用。

这个函数不要直接调用。在子类里实现,仅能被内部的Readable类调用。

所有可读流(Readable stream) 的实现必须停供一个_read方法,从底层资源里获取数据。

这个方法以下划线开头,是因为对于定义它的类是内部的,不会被用户程序直接调用。你可以在自己的扩展类中实现。

当数据可用时,通过调用readable.push(chunk)将之放到读取队列中。再次调用_read,需要继续推出更多数据。

size参数仅供参考。调用“read”可以知道知道应当抓取多少数据;其余与之无关的实现,比如TCP或TLS,则可忽略这个参数,并在可用时返回数据。例如,没有必要“等到”size个字节可用时才调用stream.push(chunk)。

readable.push(chunk[, encoding])

  • chunk {Buffer | null | String} 推入到读取队列的数据块
  • encoding {String} 字符串块的编码。必须是有效的Buffer编码,比如utf8或ascii。
  • 返回{Boolean}是否应该继续推入

注意: 这个函数必须被 Readable 实现者调用, 而不是可读流(Readable stream)的消费者.

_read()函数直到调用push(chunk)后才能被再次调用。

Readable类将数据放到读取队列,当'readable'事件触发后,被read()方法取出。push()方法会插入数据到读取队列中。如果调用了null,会触发数据结束信号 (EOF)。

这个API被设计成尽可能地灵活。比如说,你可以包装一个低级别的,具备某种暂停/恢复机制,和数据回调的数据源。这种情况下,你可以通过这种方式包装低级别来源对象:

javascript// source is an object with readStop() and readStart() 方法s,// and an `ondata` member that gets called when it has data, and// an `onend` member that gets called when the data is over.util.inherits(SourceWrapper, Readable);function SourceWrapper(options) {  Readable.call(this, options);  this._source = getLowlevelSourceObject();  var self = this;  // Every time there's data, we push it into the internal buffer.  this._source.ondata = function(chunk) {    // if push() 返回 false, then we need to stop reading from source    if (!self.push(chunk))      self._source.readStop();  };  // When the source ends, we push the EOF-signaling `null` chunk  this._source.onend = function() {    self.push(null);  };}// _read will be called when the stream wants to pull more data in// the advisory size 参数 is ignored in this case.SourceWrapper.prototype._read = function(size) {  this._source.readStart();};

类: stream.Writable

stream.Writable是个抽象类,它扩展了一个底层的实现_write(chunk, encoding, callback)方法.

参考上面的流实现程序API,来了解在你的程序里如何消费可写流。下面内容介绍了如何在你的程序里实现可写流。

new stream.Writable([options])

  • options {Object}
    • highWaterMark {Number} 当write()返回false时的缓存级别。默认=16kb,objectMode流是16。
    • decodeStrings {Boolean} 传给_write()前是否解码为字符串。默认=true
    • objectMode {Boolean}write(anyObj)是否是有效操作;如果为true,可以写任意数据,而不仅仅是Buffer/String。默认=false

请确保Writable类的扩展类中,调用构造函数以便缓冲设定能被正确初始化。

writable._write(chunk, encoding, callback)

  • chunk {Buffer | String} 要写入的数据块。总是buffer, 除非decodeStrings选项为false
  • encoding {String} 如果数据块是字符串,这个参数就是编码方式。如果是缓存,则忽略。注意,除非decodeStrings被设置为false,否则这个数据块一直是buffer。
  • callback{函数} 当你处理完数据后调用这个函数 (错误参数为可选参数)。

所以可写流(Writable stream ) 实现必须提供一个_write()方法,来发送数据给底层资源。

注意: 这个函数不能直接调用,由子类实现,仅内部可写方法可以调用。

使用标准的callback(error)方法调用回调函数,来表明写入完成或遇到错误。

如果构造函数选项中设定了decodeStrings标识,则chunk可能会是字符串而不是Buffer,encoding表明了字符串的格式。这种设计是为了支持对某些字符串数据编码提供优化处理的实现。如果你没有明确的设置decodeStringsfalse,这样你就可以安不管encoding参数,并假定chunk一直是一个缓存。

该方法以下划线开头,是因为对于定义它的类来说,这个方法是内部的,并且不应该被用户程序直接调用。你应当在你的扩充类中重写这个方法。

writable._writev(chunks, callback)

  • chunks {Array} 准备写入的数据块,每个块格式如下:{ chunk: ..., encoding: ... }.
  • callback {函数} 当你处理完数据后调用这个函数 (错误参数为可选参数)。

注意: 这个函数不能直接调用。由子类实现,仅内部可写方法可以调用.

这个函数的实现是可选的。多数情况下,没有必要实现。如果实现,将会在所有数据块缓存到写队列后调用。

类: stream.Duplex

双工流(duplex stream)同时兼具可读和可写特性,比如一个TCP socket连接。

注意stream.Duplex可以像Readable或Writable一样被扩充,实现了底层_read(sise) 和_write(chunk, encoding, callback) 方法的抽象类。

由于JavaScript并没有多重继承能力,因此这个类继承自Readable,寄生自Writable.从而让用户在双工扩展类中同时实现低级别的_read(n)方法和低级别的_write(chunk, encoding, callback)方法。

new stream.Duplex(options)

  • options {Object} 传递Writable and Readable构造函数,有以下的内容:
    • allowHalfOpen {Boolean} 默认=true。 如果设置为false,当写端结束的时候,流会自动的结束读端,反之亦然。
    • readableObjectMode {Boolean} 默认=false。将objectMode设为读端的流,如果为true,将没有效果。
    • writableObjectMode {Boolean} 默认=false。将objectMode设为写端的流,如果为true,将没有效果。

扩展自Duplex的类,确保调用了父亲的构造函数,保证缓存设置能正确初始化。

类: stream.Transform

转换流(transform class) 是双工流(duplex stream),输入输出端有因果关系,比如,zlib流或crypto流。

输入输出没有要求大小相同,块数量相同,到达时间相同。例如,一个Hash流只会在输入结束时产生一个数据块的输出;一个zlib流会产生比输入小得多或大得多的输出。

转换流(transform class) 必须实现_transform()方法,而不是_read()_write()方法,也可以实现_flush()方法(参见如下)。

new stream.Transform([options])

  • options {Object} 传递给Writable和Readable构造函数。

扩展自转换流(transform class) 的类,确保调用了父亲的构造函数,保证缓存设置能正确初始化。

transform._transform(chunk, encoding, callback)

  • chunk {Buffer | String} 准备转换的数据块。是buffer,除非decodeStrings选项设置为false
  • encoding {String} 如果数据块是字符串, 这个参数就是编码方式,否则就忽略这个参数
  • callback {函数} 当你处理完数据后调用这个函数 (错误参数为可选参数)。

注意:这个函数不能直接调用。由子类实现,仅内部可写方法可以调用.

所有的转换流(transform class) 实现必须提供 _transform方法来接收输入,并生产输出。

_transform可以做转换流(transform class)里的任何事,处理写入的字节,传给接口的写端,异步I/O,处理事情等等。

调用transform.push(outputChunk)0次或多次,从这个输入块里产生输出,依赖于你想要多少数据作为输出。

仅在当前数据块完全消费后调用这个回调。

注意,输入块可能有,也可能没有对应的输出块。如果你提供了第二个参数,将会传给push方法。如下述的例子:

javascripttransform.prototype._transform = function (data, encoding, callback) {  this.push(data);  callback();}transform.prototype._transform = function (data, encoding, callback) {  callback(null, data);}

该方法以下划线开头,是因为对于定义它的类来说,这个方法是内部的,并且不应该被用户程序直接调用。你应当在你的扩充类中重写这个方法。

transform._flush(callback)

  • callback {函数} 当你处理完数据后调用这个函数 (错误参数为可选参数)

注意:这个函数不能直接调用。由子类实现,仅内部可写方法可以调用.

某些情况下,转换操作可能需要分发一点流最后的数据。例如,Zlib流会存储一些内部状态,以便优化压缩输出。

有些时候,你可以实现_flush方法,它可以在最后面调用,当所有的写入数据被消费后,分发end告诉读端。和_transform一样,当刷新操作完毕, transform.push(chunk)为0次或更多次数。

该方法以下划线开头,是因为对于定义它的类来说,这个方法是内部的,并且不应该被用户程序直接调用。你应当在你的扩充类中重写这个方法。

事件: 'finish' and 'end'

finishend事件 分别来自Writable和Readable类。.end()事件结束后调用finish事件,所有的数据已经被_transform处理完毕,调用_flush后,所有的数据输出完毕,触发end

Example:SimpleProtocolparser v2

上面的简单协议分析例子列子可以通过使用高级别的Transform流来实现,和parseHeaderSimpleProtocol v1列子类似。

在这个示例中,输入会被导流到解析器中,而不是作为参数提供。这种做法更符合Node流的惯例。

javascriptvar util = require('util');var Transform = require('stream').Transform;util.inherits(SimpleProtocol, Transform);function SimpleProtocol(options) {  if (!(this instanceof SimpleProtocol))    return new SimpleProtocol(options);  Transform.call(this, options);  this._inBody = false;  this._sawFirstCr = false;  this._rawHeader = [];  this.header = null;}SimpleProtocol.prototype._transform = function(chunk, encoding, done) {  if (!this._inBody) {    // check if the chunk has a 

    var split = -1;    for (var i = 0; i < chunk.length; i++) {      if (chunk[i] === 10) { // '
'        if (this._sawFirstCr) {          split = i;          break;        } else {          this._sawFirstCr = true;        }      } else {        this._sawFirstCr = false;      }    }    if (split === -1) {      // still waiting for the 

      // stash the chunk, and try again.      this._rawHeader.push(chunk);    } else {      this._inBody = true;      var h = chunk.slice(0, split);      this._rawHeader.push(h);      var header = Buffer.concat(this._rawHeader).toString();      try {        this.header = JSON.parse(header);      } catch (er) {        this.emit('error', new Error('invalid simple protocol data'));        return;      }      // and let them know that we are done parsing the header.      this.emit('header', this.header);      // now, because we got some extra data, emit this first.      this.push(chunk.slice(split));    }  } else {    // from there on, just provide the data to our consumer as-is.    this.push(chunk);  }  done();};// Usage:// var parser = new SimpleProtocol();// source.pipe(parser)// Now parser is 可读流(Readable stream) that will emit 'header'// with the parsed header data.

类: stream.PassThrough

这是Transform流的简单实现,将输入的字节简单的传递给输出。它的主要用途是测试和演示。偶尔要构建某种特殊流时也会用到。

流: 内部细节

缓冲

可写流(Writable streams )和可读流(Readable stream)都会缓存数据到内部对象上,叫做_writableState.buffer_readableState.buffer

缓存的数据量,取决于构造函数是传入的highWaterMark参数。

调用stream.push(chunk)时,缓存数据到可读流(Readable stream)。在数据消费者调用stream.read()前,数据会一直缓存在内部队列中。

调用stream.write(chunk)时,缓存数据到可写流(Writable stream)。即使write()返回false

流(尤其是pipe()方法)得目的是限制数据的缓存量到一个可接受的水平,使得不同速度的源和目的不会淹没可用内存。

stream.read(0)

某些时候,你可能想不消费数据的情况下,触发底层可读流(Readable stream)机制的刷新。这种情况下可以调用stream.read(0),它总会返回null。

如果内部读取缓冲低于highWaterMark,并且流当前不在读取状态,那么调用read(0)会触发一个低级_read调用。

虽然基本上没有必要这么做。但你在Node内部的某些地方看到它确实这么做了,尤其是在Readable流类的内部。

stream.push('')

推一个0字节的字符串或缓存 (不在Object mode时)会发送有趣的副作用。 因为它是一个对stream.push()的调用,它将会结束reading进程。然而,它没有添加任何数据到可读缓冲区中,所以没有东西可供用户消费。

少数情况下,你当时没有提供数据,但你的流的消费者(或你的代码的其它部分)会通过调用stream.read(0)得知何时再次检查。在这种情况下,你可以调用 stream.push('')

到目前为止,这个功能唯一一个使用情景是在tls.CryptoStream类中,但它将在Node v0.12中被废弃。如果你发现你不得不使用stream.push(''),请考虑另一种方式。

和老版本的兼容性

v0.10版本前,可读流(Readable stream)接口比较简单,因此功能和用处也小。

  • 'data'事件会立即开始触发,而不会等待你调用read()方法。如果你需要进行某些I/O来决定如何处理数据,那么你只能将数据块储存到某种缓冲区中以防它们流失。
  • pause()方法仅供参考,而不保证生效。这意味着,即便流处于暂停状态时,你仍然需要准备接收'data'事件。

在Node v0.10中, 加入了下文所述的Readable类。为了考虑向后兼容,添加了'data'事件监听器或resume()方法被调用时,可读流(Readable stream)会切换到 "流动模式(flowing mode)"。其作用是,即便你不使用新的read()方法和'readable'事件,你也不必担心丢失'data'数据块。

大多数程序会维持正常功能。然而,下列条件下也会引入边界情况:

  • 没有添加 ['data'事件][]处理器
  • 从来没有调用resume()方法
  • 流从来没有被倒流(pipe)到任何可写目标上、

例如:

javascript// WARNING!  BROKEN!net.createServer(function(socket) {  // we add an 'end' 方法, but never consume the data  socket.on('end', function() {    // It will never get here.    socket.end('I got your message (but didnt read it)
');  });}).listen(1337);

v0.10版本前的Node,流入的消息数据会被简单的抛弃。之后的版本,socket会一直保持暂停。

这种情形下,调用resume()方法来开始工作:

javascript// Workaroundnet.createServer(function(socket) {  socket.on('end', function() {    socket.end('I got your message (but didnt read it)
');  });  // start the flow of data, discarding it.  socket.resume();}).listen(1337);

可读流(Readable stream)切换到流动模式(flowing mode),v0.10 版本前,可以使用wrap()方法将风格流包含在一个可读类里。

Object Mode

通常情况下,流仅操作字符串和缓存。

处于object mode的流,除了缓存和字符串,还可以可以读出普通JavaScript值。

在对象模式里,可读流(Readable stream) 调用stream.read(size)总会返回单个项目,无论是什么参数。

在对象模式里, 可写流(Writable stream ) 总会忽略传给stream.write(data, encoding)encoding参数。

特殊值null在对象模式里,依旧保持它的特殊性。也就说,对于对象模式的可读流(Readable stream),stream.read()返回null意味着没有更多数据,同时stream.push(null)会告知流数据结束(EOF)。

Node核心不存在对象模式的流,这种设计只被某些用户态流式库所使用。

应该在你的子类构造函数里,设置objectMode。在过程中设置不安全。

对于双工流(Duplex streams),objectMode可以用readableObjectModewritableObjectMode分别为读写端分别设置。这些选项,被转换流(Transform streams)用来实现解析和序列化。

javascriptvar util = require('util');var StringDecoder = require('string_decoder').StringDecoder;var Transform = require('stream').Transform;util.inherits(JSONParseStream, Transform);// Gets 
-delimited JSON  string data, and emits the parsed objectsfunction JSONParseStream() {  if (!(this instanceof JSONParseStream))    return new JSONParseStream();  Transform.call(this, { readableObjectMode : true });  this._buffer = '';  this._decoder = new StringDecoder('utf8');}JSONParseStream.prototype._transform = function(chunk, encoding, cb) {  this._buffer += this._decoder.write(chunk);  // split on newlines  var lines = this._buffer.split(/
?
/);  // keep the last partial line buffered  this._buffer = lines.pop();  for (var l = 0; l < lines.length; l++) {    var line = lines[l];    try {      var obj = JSON.parse(line);    } catch (er) {      this.emit('error', er);      return;    }    // push the parsed object out to the readable consumer    this.push(obj);  }  cb();};JSONParseStream.prototype._flush = function(cb) {  // Just handle any leftover  var rem = this._buffer.trim();  if (rem) {    try {      var obj = JSON.parse(rem);    } catch (er) {      this.emit('error', er);      return;    }    // push the parsed object out to the readable consumer    this.push(obj);  }  cb();};


稳定性: 3 - 稳定

net模块提供了异步网络封装,该Node.js模块包含了创建服务器/客户端的方法(调用 streams),你可以通过调用 require('net') 包含这个模块,访问方法如下所示:

const net = require('net');

net.createServer([options][, connectionListener])

创建一个TCP服务器。参数connectionListener自动给'connection'事件创建监听器。

options包含有以下默认值:

{  allowHalfOpen: false,  pauseOnConnect: false}

如果allowHalfOpen=true,当另一端socket发送FIN包时,socket不会自动发送FIN包。socket变为不可读,但仍可写。你需要显式的调用end()方法。更多信息参见'end'事件。

如果pauseOnConnect=true,当连接到来的时候相关联的socket将会暂停。它允许在初始进程不读取数据情况下,让连接在进程间传递。调用resume()从暂停的socket里读取数据。

下面是一个监听8124端口连接的应答服务器的例子:

var net = require('net');var server = net.createServer(function(c) { //'connection' listener  console.log('client connected');  c.on('end', function() {    console.log('client disconnected');  });  c.write('hello
');  c.pipe(c);});server.listen(8124, function() { //'listening' listener  console.log('server bound');});

使用telnet来测试:

telnet localhost 8124

要监听socket t/tmp/echo.sock,仅需要改倒数第三行代码,如下所示:

server.listen('/tmp/echo.sock', function() { //'listening' listener

使用nc连接到一个UNIX domain socket服务器:

nc -U /tmp/echo.sock

net.connect(options[, connectionListener])

net.createConnection(options[, connectionListener])

工厂方法,返回一个新的'net.Socket',并连接到指定的地址和端口。

当socket建立的时候,将会触发'connect'事件。

'net.Socket'有相同的方法。

对于TCP sockets,参数options因为下列参数的对象:

  • port: 客户端连接到Port的端口(必须)。

  • host: 客户端要连接到得主机。默认'localhost'.

  • localAddress: 网络连接绑定的本地接口。

  • localPort: 网络连接绑定的本地端口。

  • family : IP栈版本。默认4

对于本地域socket,参数options因为下列参数的对象:

  • path: 客户端连接到得路径(必须).

通用选项:

  • 如果allowHalfOpen=true, 当另一端socket发送FIN包时socket不会自动发送FIN包。socket变为不可读,但仍可写。你需要显式的调用end()方法。更多信息参见'end'事件。

    connectListener参数将会作为监听器添加到'connect'事件上。

下面是一个用上述方法应答服务器的客户端例子:

var net = require('net');var client = net.connect({port: 8124},    function() { //'connect' listener  console.log('connected to server!');  client.write('world!
');});client.on('data', function(data) {  console.log(data.toString());  client.end();});client.on('end', function() {  console.log('disconnected from server');});

要连接到socket/tmp/echo.sock,仅需将第二行代码改为如下的内容:

var client = net.connect({path: '/tmp/echo.sock'});

net.connect(port[, host][, connectListener])

net.createConnection(port[, host][, connectListener])

创建一个到端口port和主机host的TCP连接。如果忽略主机host,则假定为'localhost'。参数connectListener将会作为监听器添加到'connect'事件。

这是工厂方法,返回一个新的'net.Socket'

net.connect(path[, connectListener])

net.createConnection(path[, connectListener])

创建到path的unix socket连接。参数connectListener将会作为监听器添加到'connect'事件上。

这是工厂方法,返回一个新的'net.Socket'

Class: net.Server

这个类用来创建一个TCP或本地服务器。

server.listen(port[, host][, backlog][, callback])

开始接受指定端口port和主机host的连接。如果忽略主机host, 服务器将会接受任何IPv4地址(INADDR_ANY)的直接连接。端口为0,则会分配一个随机端口。

积压量(Backlog)为连接等待队列的最大长度。实际长度由您的操作系统通过 sysctl 设定,比如 linux 上的tcp_max_syn_backlogsomaxconn。这个参数默认值是511(不是512)。

这是异步函数。当服务器被绑定时会触发'listening'事件。最后一个参数callback将会作为'listening'事件的监听器。

有些用户会遇到EADDRINUSE错误,它表示另外一个服务器已经运行在所请求的端口上。处理这个情况的办法是等一段事件再重试:

server.on('error', function (e) {  if (e.code == 'EADDRINUSE') {    console.log('Address in use, retrying...');    setTimeout(function () {      server.close();      server.listen(PORT, HOST);    }, 1000);  }});

(注意:Node中的所有socket已设置了SO_REUSEADDR)

server.listen(path[, callback])

  • path {String}
  • callback {Function}

启动一个本地socket服务器,监听指定path的连接。

这是异步函数。绑定服务器后,会触发'listening'事件。最后一个参数callback将会作为'listening'事件的监听器。

UNIX上,本地域通常默认为UNIX域。参数path是文件系统路径,就和创建文件时一样,它也遵从命名规则和权限检查,并且在文件系统里可见,并持续到关闭关联。

Windows上,本地域通过命名管道实现。路径必须是以?pipe.pipe入口。任意字符串都可以,不过之后进行相同的管道命名处理,比如解决..序列。管道命名空间是平的。管道不会一直持久,当最后一个引用关闭的时候,管道将会移除。不要忘记javascript字符字符串转义要求路径使用双反斜杠,比如:

net.createServer().listen(    path.join('\?pipe', process.cwd(), 'myctl'))

server.listen(handle[, callback])

  • handle {Object}
  • callback {Function}

    handle 对象可以设置成server或socket(任意以下划线_handle开头的类),或者是{fd: <n>}对象。

这将是服务器用指定的句柄接收连接,前提是文件描述符或句柄已经绑定到端口或域socket。

Windows不支持监听文件句柄。

这是异步函数。当服务器已经被绑定,将会触发'listening'事件。最后一个参数callback将会作为'listening'事件的监听器。

server.listen(options[, callback])

  • options {Object} - 必须有。支持以下属性:
    • port {Number} - 可选。
    • host {String} - 可选。
    • backlog {Number} - 可选。
    • path {String} - 可选。
    • exclusive {Boolean} - 可选。
  • callback {Function} - 可选。

options的属性:端口port,主机host,和backlog,以及可选参数callback函数,他们在一起调用server.listen(port, [host], [backlog], [callback])。还有,参数path可以用来指定UNIX socket。

如果参数exclusivefalse(默认值),集群进程将会使用同一个句柄,允许连接共享。当参数exclusivetrue时,句柄不会共享,如果共享端口会返回错误。监听独家端口例子如下:

server.listen({  host: 'localhost',  port: 80,  exclusive: true});

server.close([callback])

服务器停止接收新的连接,保持现有连接。这是异步函数,当所有连接结束的时候服务器会关闭,并会触发'close'事件。你可以传一个回调函数来监听'close' 事件。如果存在,将会调用回调函数,错误(如果有)作为唯一参数。

server.address()

操作系统返回绑定的地址,协议族名和服务器端口。查找哪个端口已经被系统绑定时,非常有用。返回的对象有3个属性,比如:{ port: 12346, family: 'IPv4', address: '127.0.0.1' }

例如:

var server = net.createServer(function (socket) {  socket.end("goodbye
");});// grab a random port.server.listen(function() {  address = server.address();  console.log("opened server on %j", address);});

'listening'事件触发前,不要调用server.address()

server.unref()

如果这是事件系统中唯一一个活动的服务器,调用unref将允许程序退出。如果服务器已被unref,则再次调用unref并不会产生影响。

server.ref()

unref相反,如果这是唯一的服务器,在之前被unref了的服务器上调用ref将不会让程序退出(默认行为)。如果服务器已经被ref,则再次调用ref并不会产生影响。

server.maxConnections

设置这个选项后,当服务器连接数超过数量时拒绝新连接。

一旦已经用child_process.fork()方法将socket发送给子进程, 就不推荐使用这个选项。

server.connections

已经抛弃这个函数。请用server.getConnections()代替。服务器上当前连接的数量。

当调用child_process.fork()发送一个socket给子进程时,它将变为null。 要轮询子进程来获取当前活动连接的数量,请用server.getConnections代替。

server.getConnections(callback)

异步获取服务器当前活跃连接的数量。当socket发送给子进程后才有效;

回调函数有2个参数errcount

net.Server是事件分发器EventEmitter, 有以下事件:

事件: 'listening'

当服务器调用server.listen绑定后会触发。

事件:'connection'

  • {Socket object} 连接对象

当新连接创建后会被触发。socketnet.Socket实例。

事件: 'close'

服务器关闭时会触发。注意,如果存在连接,这个事件不会被触发直到所有的连接关闭。

事件: 'error'

  • {Error Object}

发生错误时触发。'close'事件将被下列事件直接调用。请查看server.listen例子。

Class: net.Socket

这个对象是TCP或UNIX Socket的抽象。net.Socket实例实现了一个双工流接口。他们可以在用户创建客户端(使用connect())时使用,或者由Node创建它们,并通过connection服务器事件传递给用户。

new net.Socket([options])

构造一个新的socket对象。

options对象有以下默认值:

{ fd: null  allowHalfOpen: false,  readable: false,  writable: false}

参数fd允许你指定一个存在的文件描述符。将readable和(或)writable设为true,允许在这个socket上读和(或)写(注意,仅在参数fd有效时)。关于allowHalfOpen,参见createServer()'end'事件。

socket.connect(port[, host][, connectListener])

socket.connect(path[, connectListener])

使用传入的socket打开一个连接。如果指定了端口port和主机host,TCP socket将打开socket。如果忽略参数host,则默认为localhost。如果指定了 path,socket将会被指定路径的unix socket打开。

通常情况不需要使用这个函数,比如使用net.createConnection打开socket。只有你实现了自己的socket时才会用到。

这是异步函数。当'connect'事件被触发时,socket已经建立。如果这是问题连接,'connect'事件不会被触发,将会抛出'error'事件。

参数connectListener将会作为监听器添加到'connect'事件。

socket.bufferSize

socket.bufferSize是net.Socket的一个属性,用于socket.write()。它能够帮助用户获取更快的运行速度。计算机不能一直处于写入大量数据状态--网络连接可能太慢。Node在内部会将排队数据写入到socket,并在网络可用时发送。(内部实现:轮询socket的文件描述符直到变为可写)。

这种内部缓冲的缺点是会增加内存使用量。这个属性表示当前准备写的缓冲字符数。(字符的数量等于准备写入的字节的数量,但是缓冲区可能包含字符串,这些字符串是惰性编码的,所以准确的字节数还无法知道)。

遇到很大增长很快的bufferSize时,用户可用尝试用pause()resume()来控制字符流。

socket.setEncoding([encoding])

设置socket的编码为可读流。更多信息参见stream.setEncoding()

socket.write(data[, encoding][, callback])

在socket上发送数据。第二个参数指定了字符串的编码,默认是UTF8编码。

如果所有数据成功刷新到内核缓冲区,返回true。如果数据全部或部分在用户内存里,返回false。当缓冲区为空的时候会触发'drain'

当数据最终被完整写入的的时候,可选的callback参数会被执行,但不一定会马上执行。

socket.end([data][, encoding])

半关闭socket。例如,它发送一个FIN包。可能服务器仍在发送数据。

如果参数data不为空,等同于调用socket.write(data, encoding)后再调用socket.end()

socket.destroy()

确保没有I/O活动在这个套接字上。只有在错误发生情况下才需要。(处理错误等等)。

socket.pause()

暂停读取数据。就是说,不会再触发data事件。对于控制上传非常有用。

socket.resume()

调用pause()后想恢复读取数据。

socket.setTimeout(timeout[, callback])

socket闲置时间超过timeout毫秒后 ,将socket设置为超时。

触发空闲超时事件时,socket将会收到'timeout'事件,但是连接不会被断开。用户必须手动调用end()destroy()这个socket。

如果timeout= 0,那么现有的闲置超时会被禁用

可选的callback参数将会被添加成为'timeout'事件的一次性监听器。

socket.setNoDelay([noDelay])

禁用纳格(Nagle)算法。默认情况下TCP连接使用纳格算法,在发送前他们会缓冲数据。将noDelay设置为true将会在调用socket.write()时立即发送数据。noDelay默认值为true

socket.setKeepAlive([enable][, initialDelay])

禁用/启用长连接功能,并在发送第一个在闲置socket上的长连接 probe 之前,可选地设定初始延时。默认为false。

设定initialDelay(毫秒),来设定收到的最后一个数据包和第一个长连接probe之间的延时。将initialDelay设为0,将会保留默认(或者之前)的值。默认值为0。

socket.address()

操作系统返回绑定的地址,协议族名和服务器端口。返回的对象有3个属性,比如{ port: 12346, family: 'IPv4', address: '127.0.0.1' }

socket.unref()

如果这是事件系统中唯一一个活动的服务器,调用unref将允许程序退出。如果服务器已被unref,则再次调用unref并不会产生影响。

socket.ref()

unref相反,如果这是唯一的服务器,在之前被unref了的服务器上调用ref将不会让程序退出(默认行为)。如果服务器已经被ref,则再次调用ref并不会产生影响。

socket.remoteAddress

远程的IP地址字符串,例如:'74.125.127.100'或者'2001:4860:a005::68'

socket.remoteFamily

远程IP协议族字符串,比如'IPv4'或者'IPv6'

socket.remotePort

远程端口,数字表示,例如:80或者21

socket.localAddress

网络连接绑定的本地接口远程客户端正在连接的本地IP地址,字符串表示。例如,如果你在监听'0.0.0.0'而客户端连接在'192.168.1.1',这个值就会是 '192.168.1.1'。

socket.localPort

本地端口地址,数字表示。例如:80或者21

socket.bytesRead

接收到得字节数。

socket.bytesWritten

发送的字节数。

net.Socket是事件分发器EventEmitter的实例, 有以下事件:

事件: 'lookup'

在解析域名后,但在连接前,触发这个事件。对UNIX sokcet不适用。

  • err {Error | Null} 错误对象。参见dns.lookup().
  • address {String} IP地址。
  • family {String | Null} 地址类型。参见dns.lookup().

事件: 'connect'

当成功建立socket连接时触发。参见connect()

事件: 'data'

  • {Buffer object}

当接收到数据时触发。参数data可以是BufferString。使用socket.setEncoding()设定数据编码。(更多信息参见Readable Stream)。

Socket触发一个'data'事件时,如果没有监听器,数据将会丢失。

事件: 'end'

当socket另一端发送FIN包时,触发该事件。

默认情况下(allowHalfOpen == false),一旦socket将队列里的数据写完毕,socket将会销毁它的文件描述符。如果allowHalfOpen == true,socket不会从它这边自动调用end(),使的用户可以随意写入数据,而让用户端自己调用end()。

事件: 'timeout'

当socket空闲超时时触发,仅是表明socket已经空闲。用户必须手动关闭连接。

参见:socket.setTimeout()

事件: 'drain'

当写缓存为空得时候触发。可用来控制上传。

参见:socket.write()的返回值。

事件: 'error'

  • {Error object}

错误发生时触发。以下事件将会直接触发'close'事件。

事件: 'close'

  • had_error {Boolean} 如果socket传输错误,为true

当socket完全关闭时触发。参数had_error是boolean,它表示是否因为传输错误导致socket关闭。

net.isIP(input)

测试是否输入的为IP地址。字符串无效时返回0。IPV4情况下返回4,IPV6情况下返回6.

net.isIPv4(input)

如果输入的地址为IPV4,返回true,否则返回false。

net.isIPv6(input)

如果输入的地址为IPV6,返回true,否则返回false。


稳定性: 3 - 稳定

V8提供了强大的调试工具,可以通过TCP protocol从外部访问。Node内置这个调试工具客户端。使用这个调试器的方法是,以debug参数启动Node.js,将会出现提示,指示调试器成功启动:

% node debug myscript.js< debugger listening on port 5858connecting... okbreak in /home/indutny/Code/git/indutny/myscript.js:1  1 x = 5;  2 setTimeout(function () {  3   debugger;debug>

Node的调试器不支持所有的命令,但是简单的步进和检查还是可以的。在代码里嵌入debugger;,可以设置断点。

例:myscript.js代码如下:

// myscript.jsx = 5;setTimeout(function () {  debugger;  console.log("world");}, 1000);console.log("hello");

如果启动debugger,它会断在第四行:

% node debug myscript.js< debugger listening on port 5858connecting... okbreak in /home/indutny/Code/git/indutny/myscript.js:1  1 x = 5;  2 setTimeout(function () {  3   debugger;debug> cont< hellobreak in /home/indutny/Code/git/indutny/myscript.js:3  1 x = 5;  2 setTimeout(function () {  3   debugger;  4   console.log("world");  5 }, 1000);debug> nextbreak in /home/indutny/Code/git/indutny/myscript.js:4  2 setTimeout(function () {  3   debugger;  4   console.log("world");  5 }, 1000);  6 console.log("hello");debug> replPress Ctrl + C to leave debug repl> x5> 2+24debug> next< worldbreak in /home/indutny/Code/git/indutny/myscript.js:5  3   debugger;  4   console.log("world");  5 }, 1000);  6 console.log("hello");  7debug> quit%

repl命令能执行远程代码;next能步进到下一行。此外可以输入help查看哪些命令可用。

监视器-Watchers

调试的时候可以查看表达式和变量。每个断点处,监视器都会显示上下文。

输入watch("my_expression")开始监视表达式,watchers显示活跃的监视器。输入unwatch("my_expression")可以移除监视器。

命令参考-Commands reference

步进-Stepping

  • cont, c- 继续执行
  • next, n- Step next
  • step, s- Step in
  • out, o- Step out
  • pause- 暂停 (类似开发工具的暂停按钮)

断点Breakpoints

  • setBreakpoint(), sb()- 当前行设置断点
  • setBreakpoint(line), sb(line)- 在指定行设置断点
  • setBreakpoint('fn()'), sb(...)- 在函数里的第一行设置断点
  • setBreakpoint('script.js', 1), sb(...)- 在 script.js 第一行设置断点。
  • clearBreakpoint, cb(...)- 清除断点

也可以在尚未加载的文件里设置断点:

% ./node debug test/fixtures/break-in-module/main.js< debugger listening on port 5858connecting to port 5858... okbreak in test/fixtures/break-in-module/main.js:1  1 var mod = require('./mod.js');  2 mod.hello();  3 mod.hello();debug> setBreakpoint('mod.js', 23)Warning: script 'mod.js' was not loaded yet.  1 var mod = require('./mod.js');  2 mod.hello();  3 mod.hello();debug> cbreak in test/fixtures/break-in-module/mod.js:23 21 22 exports.hello = function() { 23   return 'hello from module'; 24 }; 25debug>

信息Info

  • backtrace, bt- 打印当前执行框架的backtrace
  • list(5)- 显示脚本代码的5行上下文(之前5行和之后5行)
  • watch(expr)- 监视列表里添加表达式
  • unwatch(expr)- 从监视列表里删除表达式
  • watchers- 显示所有的监视器和它们的值(每个断点都会自动列出)
  • repl- 在所调试的脚本的上下文中,打开调试器的repl

执行控制Execution control

  • run- 运行脚本 (开始调试的时候自动运行)
  • restart- 重新运行脚本
  • kill- 杀死脚本

杂项Various

  • scripts- 列出所有已经加载的脚本
  • version- 显示v8版本

高级应用Advanced Usage

V8调试器可以用两种方法启用和访问,--debug命令启动调试,或向已经启动Node发送SIGUSR1

一旦一个进程进入调试模式,它可以被node调试器连接。调试器可以通过pid或URI来连接。

  • node debug -p <pid>- 通过pid连接进程
  • node debug <URI>- 通过URI(比如localhost:5858)连接进程w。


稳定性: 3 - 稳定

Node.js字符串解码器(string_decoder)模块的使用是通过require('string_decoder')实现的。

Node.js字符串解码器(string_decoder)用于将缓存(buffer)解码为字符串。这是buffer.toString()的简单接口,提供了utf8支持。

var StringDecoder = require('string_decoder').StringDecoder;var decoder = new StringDecoder('utf8');var cent = new Buffer([0xC2, 0xA2]);console.log(decoder.write(cent));var euro = new Buffer([0xE2, 0x82, 0xAC]);console.log(decoder.write(euro));

Class: StringDecoder

接受一个参数encoding,默认值为utf8

decoder.write(buffer)

返回解码后的字符串。

decoder.end()

返回buffer里剩下的末尾字节。


稳定性: 4 - API 冻结

Node.js系统(OS)模块提供一些与基本的操作系统有关的函数。

使用require('os')访问这个模块,如下所示:

const os = require('os');

os.tmpdir()

用于返回操作系统的默认临时文件夹。

os.endianness()

用于返回CPU的字节序,可能的是"BE"或"LE"。

os.hostname()

用于返回操作系统的主机名。

os.type()

用于返回操作系统名。

os.platform()

用于返回操作系统名

os.arch()

用于返回操作系统CPU架构,可能的值有"x64"、"arm"和"ia32"。

os.release()

用于返回操作系统的发行版本

os.uptime()

用于返回操作系统运行的时间,以秒为单位。

os.loadavg()

用于显示原文其他翻译纠错返回一个包含1、5、15分钟平均负载的数组。

平均负载是系统的一个指标,操作系统计算,用一个很小的数字表示。理论上来说,平均负载最好比系统里的CPU低。

平均负载是一个非常UNIX-y的概念,windows系统没有相同的概念。所以windows总是返回[0, 0, 0]

os.totalmem()

用于返回系统内存总量,单位为字节。

os.freemem()

用于返回操作系统空闲内存量,单位是字节。

os.cpus()

用于返回一个对象数组,包含所安装的每个CPU/内核的信息:型号、速度(单位 MHz)、时间(一个包含user、nice、sys、idle和irq所使用CPU/内核毫秒数的对象)。

os.cpus的例子:

[ { model: 'Intel(R) Core(TM) i7 CPU         860  @ 2.80GHz',    speed: 2926,    times:     { user: 252020,       nice: 0,       sys: 30340,       idle: 1070356870,       irq: 0 } },  { model: 'Intel(R) Core(TM) i7 CPU         860  @ 2.80GHz',    speed: 2926,    times:     { user: 306960,       nice: 0,       sys: 26980,       idle: 1071569080,       irq: 0 } },  { model: 'Intel(R) Core(TM) i7 CPU         860  @ 2.80GHz',    speed: 2926,    times:     { user: 248450,       nice: 0,       sys: 21750,       idle: 1070919370,       irq: 0 } },  { model: 'Intel(R) Core(TM) i7 CPU         860  @ 2.80GHz',    speed: 2926,    times:     { user: 256880,       nice: 0,       sys: 19430,       idle: 1070905480,       irq: 20 } },  { model: 'Intel(R) Core(TM) i7 CPU         860  @ 2.80GHz',    speed: 2926,    times:     { user: 511580,       nice: 20,       sys: 40900,       idle: 1070842510,       irq: 0 } },  { model: 'Intel(R) Core(TM) i7 CPU         860  @ 2.80GHz',    speed: 2926,    times:     { user: 291660,       nice: 0,       sys: 34360,       idle: 1070888000,       irq: 10 } },  { model: 'Intel(R) Core(TM) i7 CPU         860  @ 2.80GHz',    speed: 2926,    times:     { user: 308260,       nice: 0,       sys: 55410,       idle: 1071129970,       irq: 880 } },  { model: 'Intel(R) Core(TM) i7 CPU         860  @ 2.80GHz',    speed: 2926,    times:     { user: 266450,       nice: 1480,       sys: 34920,       idle: 1072572010,       irq: 30 } } ]

os.networkInterfaces()

获得网络接口列表的方法如下所示:

{ lo:   [ { address: '127.0.0.1',       netmask: '255.0.0.0',       family: 'IPv4',       mac: '00:00:00:00:00:00',       internal: true },     { address: '::1',       netmask: 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',       family: 'IPv6',       mac: '00:00:00:00:00:00',       internal: true } ],  eth0:   [ { address: '192.168.1.108',       netmask: '255.255.255.0',       family: 'IPv4',       mac: '01:02:03:0a:0b:0c',       internal: false },     { address: 'fe80::a00:27ff:fe4e:66a1',       netmask: 'ffff:ffff:ffff:ffff::',       family: 'IPv6',       mac: '01:02:03:0a:0b:0c',       internal: false } ] }

os.EOL

定义了操作系统的End-of-line的常量。


稳定性: 3 - 稳定

本节将介绍Node.js的DNS模块,你可以通过调用require('dns')来访问DNS模块。

DNS模块包含的函数属于2个不同的分类:

1)使用系统底层的特性,完成名字解析,这个过程不需要网络通讯,这个分类仅有一个函数:dns.lookup开发者在同一个系统里名字解析都是用 dns.lookup

下面的例子解析了www.google.com

var dns = require('dns');dns.lookup('www.google.com', function onLookup(err, addresses, family) {  console.log('addresses:', addresses);});

2)连接到DNS服务器进行名字解析,始终使用网络来进行域名查询。这个分类包含除了dns.lookup外的所有函数。这些函数不会和dns.lookup使用同一套配置文件。如果你不想使用系统底层的特性来进行名字解析,而想进行DNS查询的话,可以使用这个分类的函数。

下面的例子,解析了'www.google.com',并反向解析返回的IP地址:

var dns = require('dns');dns.resolve4('www.google.com', function (err, addresses) {  if (err) throw err;  console.log('addresses: ' + JSON.stringify(addresses));  addresses.forEach(function (a) {    dns.reverse(a, function (err, hostnames) {      if (err) {        throw err;      }      console.log('reverse for ' + a + ': ' + JSON.stringify(hostnames));    });  });});

更多细节参考Implementation considerations section

dns.lookup(hostname[, options], callback)

将域名(比如'google.com')解析为第一条找到的记录A (IPV4)或AAAA(IPV6)。参数options可以是一个对象或整数。如果没有提供options,IP v4和 v6地址都可以。如果options是整数,则必须是46

options参数可能是包含familyhints两个属性的对象。这两个属性都是可选的。如果提供了family,则必须是46,否则,IP v4和v6地址都可以。如果提供了hints,可以是一个或者多个getaddrinfo标志,若不提供,没有标志会传给getaddrinfo。多个标志位可以通过或运算来整合。以下的例子展示如何使用options

{  family: 4,  hints: dns.ADDRCONFIG | dns.V4MAPPED}

参见supported getaddrinfo flags 查看更多的标志位。

回调函数包含参数 (err, address, family)address参数表示IP v4或v6地址。family参数是4或6,表示address家族(不一定是之前传入lookup的值)。

出错时,参数errError对象,err.code是错误代码。请记住,err.code等于'ENOENT',不仅可能是因为域名不存在,还有可能是是其他原因,比如没有可用文件描述符。

dns.lookup不必和DNS协议有关系。它使用了操作系统的特性,能将名字和地址关联。

实现这些东西也许很简单,但是对于 Node.js 程序来说都重要,所以在使用前请花点时间阅读Implementation considerations section

dns.lookupService(address, port, callback)

使用getnameinfo解析传入的地址和端口为域名和服务。

这个回调函数的参数是(err, hostname, service)hostnameservice都是字符串 (比如'localhost''http')。

出错时,参数errError对象,err.code是错误代码。

dns.resolve(hostname[, rrtype], callback)

将一个域名(如'google.com')解析为一个rrtype指定记录类型的数组。

有效的rrtypes值为:

  • 'A' (IPV4地址,默认)
  • 'AAAA' (IPV6地址)
  • 'MX' (邮件交换记录)
  • 'TXT' (text记录)
  • 'SRV' (SRV记录)
  • 'PTR' (用来反向IP查找)
  • 'NS' (域名服务器记录)
  • 'CNAME' (别名记录)
  • 'SOA' (授权记录的初始值)

回调参数为(err, addresses). 其中addresses中每一项的类型都取决于记录类型,详见下文对应的查找方法。

出错时,参数errError 对象,err.code是错误代码。

dns.resolve4(hostname, callback)

dns.resolve()类似,仅能查询IPv4 (A记录)。addressesIPv4地址数组 (比如,['74.125.79.104', '74.125.79.105', '74.125.79.106'])。

dns.resolve6(hostname, callback)

dns.resolve4()类似,仅能查询 IPv4(AAAA查询)。

dns.resolveMx(hostname, callback)

dns.resolve()类似,仅能查询邮件交换(MX记录)。

addresses是MX记录数组,每一个包含优先级和交换属性(比如,[{'priority': 10, 'exchange': 'mx.example.com'},...])。

dns.resolveTxt(hostname, callback)

dns.resolve()类似,仅能进行文本查询 (TXT记录)。addresses是2-d文本记录数组。(比如,[ ['v=spf1 ip4:0.0.0.0 ', '~all' ] ])。每个子数组包含一条记录的TXT块。根据使用情况可以连接在一起,也可单独使用。

dns.resolveSrv(hostname, callback)

dns.resolve()类似,仅能进行服务记录查询 (SRV记录)。addresseshostname可用的SRV记录数组。SRV记录属性有优先级(priority),权重(weight), 端口(port), 和名字(name) (比如,[{'priority': 10, 'weight': 5, 'port': 21223, 'name': 'service.example.com'}, ...])。

dns.resolveSoa(hostname, callback)

dns.resolve()类似,仅能查询权威记录(SOA记录)。

addresses是包含以下结构的对象:

{  nsname: 'ns.example.com',  hostmaster: 'root.example.com',  serial: 2013101809,  refresh: 10000,  retry: 2400,  expire: 604800,  minttl: 3600}

dns.resolveNs(hostname, callback)

dns.resolve()类似,仅能进行域名服务器记录查询(NS记录)。addresses是域名服务器记录数组(hostname可以使用) (比如,['ns1.example.com', 'ns2.example.com'])。

dns.resolveCname(hostname, callback)

dns.resolve()类似,仅能进行别名记录查询 (CNAME记录)。addresses是对hostname可用的别名记录数组 (比如,['bar.example.com'])。

dns.reverse(ip, callback)

反向解析IP地址,返回指向该IP地址的域名数组。

回调函数参数(err, hostnames)

出错时,参数errError对象,err.code是错误代码。

dns.getServers()

返回一个用于当前解析的IP地址的数组的字符串。

dns.setServers(servers)

指定一组IP地址作为解析服务器。

如果你给地址指定了端口,端口会被忽略,因为底层库不支持。

传入无效参数,会抛出以下错误:

Error codes

每个DNS查询都可能返回以下错误:

  • dns.NODATA: DNS服务器返回无数据应答。
  • dns.FORMERR: DNS服务器声称查询格式错误。
  • dns.SERVFAIL: DNS服务器返回一般失败。
  • dns.NOTFOUND: 没有找到域名。
  • dns.NOTIMP: DNS服务器未实现请求的操作。
  • dns.REFUSED: DNS服务器拒绝查询。
  • dns.BADQUERY: DNS查询格式错误。
  • dns.BADNAME: 域名格式错误。
  • dns.BADFAMILY: 地址协议不支持。
  • dns.BADRESP: DNS回复格式错误。
  • dns.CONNREFUSED: 无法连接到DNS服务器。
  • dns.TIMEOUT: 连接DNS服务器超时。
  • dns.EOF: 文件末端。
  • dns.FILE: 读文件错误。
  • dns.NOMEM: 内存溢出。
  • dns.DESTRUCTION: 通道被摧毁。
  • dns.BADSTR: 字符串格式错误。
  • dns.BADFLAGS: 非法标识符。
  • dns.NONAME: 所给主机不是数字。
  • dns.BADHINTS: 非法HINTS标识符。
  • dns.NOTINITIALIZED: c c-ares库尚未初始化。
  • dns.LOADIPHLPAPI: 加载iphlpapi.dll出错。
  • dns.ADDRGETNETWORKPARAMS: 无法找到GetNetworkParams函数。
  • dns.CANCELLED: 取消DNS查询。

支持的 getaddrinfo 标志

以下内容可作为hints标志传给dns.lookup

  • dns.ADDRCONFIG: 返回当前系统支持的地址类型。例如,如果当前系统至少配置了一个IPv4地址,则返回IPv4地址。
  • dns.V4MAPPED: 如果指定了IPv6家族, 但是没有找到IPv6地址,将返回IPv4映射的IPv6地址。

Implementation considerations

虽然dns.lookupdns.resolve*/dns.reverse函数都能实现网络名和网络地址的关联,但是他们的行为不太一样。这些不同点虽然很巧妙,但是会对Node.js程序产生显著的影响。

dns.lookup

dns.lookup和绝大多数程序一样使用了相同的系统特性。例如,dns.lookupping命令用相同的方法解析了一个指定的名字。多数类似POSIX的系统,dns.lookup函数可以通过改变nsswitch.conf(5)和/或resolv.conf(5)的设置调整。如果改变这些文件将会影响系统里的其他应用。

虽然,JavaScript调用是异步的,它的实现是同步的调用libuv线程池里的getaddrinfo(3)。因为libuv线程池固定大小,所以如果调用getaddrinfo(3) 的时间太长,会使的池里的其他操作(比如文件操作)性能降低。为了降低这个风险,可以通过增加'UV_THREADPOOL_SIZE'的值,让它超过4,来调整libuv线程池大小,更多信息参见[the official libuvdocumentation](http://docs.libuv.org/en/latest/threadpool.html)。

dns.resolve, functions starting with dns.resolve and dns.reverse

这些函数的实现和dns.lookup不大相同。他们不会用到getaddrinfo(3),而是始终进行网络查询。这些操作都是异步的,和libuv线程池无关。

因此,这些操作对于其他线程不会产生负面影响,这和dns.lookup不同。

它们不会用到dns.lookup的配置文件(例如 ,/etc/hosts_)。


稳定性: 5 - 锁定

Node.js定时器模块提供了全局API,用于在以后的某个时间段调用函数。

所有的定时器函数都是全局的。不需要通过require()就可以访问。

setTimeout(callback, delay[, arg][, ...])

delay毫秒之后执行callback。返回timeoutObject对象,可能会用来clearTimeout()。你也可以给回调函数传参数。

需要注意,你的回调函数可能不会非常准确的在delay毫秒后执行,Node.js不保证回调函数的精确时间和执行顺序。回调函数会尽量的靠近指定的时间。

clearTimeout(timeoutObject)

阻止一个timeout被触发。

setInterval(callback, delay[, arg][, ...])

每隔delay毫秒就重复执行callback。返回timeoutObject对象,可能会用来clearTimeout()。你也可以给回调函数传参数。

clearInterval(intervalObject)

阻止一个interval被触发。

unref()

setTimeoutsetInterval所返回的值,拥有timer.unref()方法,它能让你创建一个活动的定时器,但是它所在的事件循环中如果仅剩它一个定时器,将不会保持程序运行。如果定时器已经调用了unref,再次调用将无效。

setTimeout场景中,当你使用unref并创建了一个独立定时器它将会唤醒事件循环。创建太多的这样的东西会影响事件循环性能,所以谨慎使用。

ref()

如果你之前已经使用unref()一个定时器,就可以使用ref()来明确的请求定时器保持程序打开状态。如果计时器已经调用了ref(),再次调用将无效。

setImmediate(callback[, arg][, ...])

setTimeoutsetInterval事件前,在输入/输出事件后,安排一个callback"immediate"立即执行。

immediates的回调以它们创建的顺序加入队列。整个回调队列会在事件循环迭代中执行。如果你将immediates加入到一个正在执行回调中,那么将不会触发immediate,直到下次事件循环迭代。

clearImmediate(immediateObject)

用于停止一个immediate的触发。


稳定性: 2 - 不稳定

Node.js域包含了能把不同的IO操作看成单独组的方法。如果任何一个注册到域的事件或者回调触发error事件,或者抛出一个异常,则域就会接收到通知,而不是在process.on('uncaughtException')处理程序中丢失错误的上下文,也不会使程序立即以错误代码退出。

警告:不要忽视错误!

你不能将域错误处理程序看做错误发生时就关闭进程的一个替代方案。

根据JavaScript中抛出异常的工作原理,基本上没有方法可以安全的“回到原先离开的位置”,在不泄露引用,或者不造成一些其他未定义的状态下。

响应抛出错误最安全的方法就是关闭进程。一个正常的服务器会可能有很多活跃的连接,因为某个错误就关闭所有连接显然是不合理的。

比较好的方法是给触发错误的请求发送错误响应,让其他连接正常工作时,停止监听触发错误的人的新请求。

按这种方法,和集群(cluster)模块可以协同工作,当某个进程遇到错误时,主进程可以复制一个新的进程。对于Node程序,终端代理或者注册的服务,可以留意错误并做出反应。

举例来说,下面的代码就不是好办法:

javascript// XXX WARNING!  BAD IDEA!var d = require('domain').create();d.on('error', function(er) {  // The error won't crash the process, but what it does is worse!  // Though we've prevented abrupt process restarting, we are leaking  // resources like crazy if this ever happens.  // This is no better than process.on('uncaughtException')!  console.log('error, but oh well', er.message);});d.run(function() {  require('http').createServer(function(req, res) {    handleRequest(req, res);  }).listen(PORT);});

通过使用域的上下文,并将程序切为多个工作进程,我们能够更合理的响应,处理错误更安全:

javascript// 好一些的做法!var cluster = require('cluster');var PORT = +process.env.PORT || 1337;if (cluster.isMaster) {  // In real life, you'd probably use more than just 2 workers,  // and perhaps not put the master and worker in the same file.  //  // You can also of course get a bit fancier about logging, and  // implement whatever custom logic you need to prevent DoS  // attacks and other bad behavior.  //  // See the options in the cluster documentation.  //  // The important thing is that the master does very little,  // increasing our resilience to unexpected errors.  cluster.fork();  cluster.fork();  cluster.on('disconnect', function(worker) {    console.error('disconnect!');    cluster.fork();  });} else {  // the worker  //  // This is where we put our bugs!  var domain = require('domain');  // See the cluster documentation for more details about using  // worker processes to serve requests.  How it works, caveats, etc.  var server = require('http').createServer(function(req, res) {    var d = domain.create();    d.on('error', function(er) {      console.error('error', er.stack);      // Note: we're in dangerous territory!      // By definition, something unexpected occurred,      // which we probably didn't want.      // Anything can happen now!  Be very careful!      try {        // make sure we close down within 30 seconds        var killtimer = setTimeout(function() {          process.exit(1);        }, 30000);        // But don't keep the process open just for that!        killtimer.unref();        // stop taking new requests.        server.close();        // Let the master know we're dead.  This will trigger a        // 'disconnect' in the cluster master, and then it will fork        // a new worker.        cluster.worker.disconnect();        // try to send an error to the request that triggered the problem        res.statusCode = 500;        res.setHeader('content-type', 'text/plain');        res.end('Oops, there was a problem!
');      } catch (er2) {        // oh well, not much we can do at this point.        console.error('Error sending 500!', er2.stack);      }    });    // Because req and res were created before this domain existed,    // we need to explicitly add them.    // See the explanation of implicit vs explicit binding below.    d.add(req);    d.add(res);    // Now run the handler function in the domain.    d.run(function() {      handleRequest(req, res);    });  });  server.listen(PORT);}// This part isn't important.  Just an example routing thing.// You'd put your fancy application logic here.function handleRequest(req, res) {  switch(req.url) {    case '/error':      // We do some async stuff, and then...      setTimeout(function() {        // Whoops!        flerb.bark();      });      break;    default:      res.end('ok');  }}

错误对象的附加内容

任何时候一个错误被路由传到一个域的时,会添加几个字段。

  • error.domain 第一个处理错误的域
  • error.domainEmitter 用这个错误对象触发'error'事件的事件分发器
  • error.domainBound 绑定到domain的回调函数,第一个参数是error。
  • error.domainThrown boolean值,表明是抛出错误,分发,或者传递给绑定的回到函数。

隐式绑定

新分发的对象(包括流对象(Stream objects),请求(requests),响应(responses)等)会隐式的绑定到当前正在使用的域中。

另外,传递给底层事件循环(比如fs.open或其他接收回调的方法)的回调函数将会自动的绑定到这个域。如果他们抛出异常,域会捕捉到错误信息。

为了避免过度使用内存,域对象不会象隐式的添加为有效域的子对象。如果这样做的话,很容易影响到请求和响应对象的垃圾回收。

如果你想将域对象作为子对象嵌入到父域里,就必须显式的添加它们。

隐式绑定路由抛出的错误和'error'事件,但是不会注册事件分发器到域,所以domain.dispose()不会关闭事件分发器。隐式绑定仅需注意抛出的错误和 'error'事件。

显式绑定

有时候正在使用的域并不是某个事件分发器的域。或者说,事件分发器可能在某个域里创建,但是被绑定到另外一个域里。

例如,HTTP服务器使用正一个域对象,但我们希望可以每一个请求使用一个不同的域。

这可以通过显式绑定来实现。

例如:

// create a top-level domain for the servervar serverDomain = domain.create();serverDomain.run(function() {  // server is created in the scope of serverDomain  http.createServer(function(req, res) {    // req and res are also created in the scope of serverDomain    // however, we'd prefer to have a separate domain for each request.    // create it first thing, and add req and res to it.    var reqd = domain.create();    reqd.add(req);    reqd.add(res);    reqd.on('error', function(er) {      console.error('Error', er, req.url);      try {        res.writeHead(500);        res.end('Error occurred, sorry.');      } catch (er) {        console.error('Error sending 500', er, req.url);      }    });  }).listen(1337);});

domain.create()

  • return: {Domain}

用于返回一个新的域对象。

Class: Domain

这个类封装了将错误和没有捕捉到的异常到有效对象功能。

域是EventEmitter的子类. 监听它的error事件来处理捕捉到的错误。

domain.run(fn)

  • fn {Function}

在域的上下文运行提供的函数,隐式的绑定了所有的事件分发器,计时器和底层请求。

这是使用域的基本方法。

例如:

var d = domain.create();d.on('error', function(er) {  console.error('Caught error!', er);});d.run(function() {  process.nextTick(function() {    setTimeout(function() { // simulating some various async stuff      fs.open('non-existent file', 'r', function(er, fd) {        if (er) throw er;        // proceed...      });    }, 100);  });});

这个例子里程序不会崩溃,而会触发d.on('error')

domain.members

  • {Array}

显式添加到域里的计时器和事件分发器数组。

domain.add(emitter)

  • emitter {EventEmitter | Timer} 添加到域里的计时器和事件分发器

显式地将一个分发器添加到域。如果分发器调用的事件处理函数抛出错误,或者分发器遇到error事件,将会导向域的error事件,和隐式绑定一样。

对于setIntervalsetTimeout返回的计时器同样适用。如果这些回调函数抛出错误,将会被域的'error'处理器捕捉到。

如果计时器或分发器已经绑定到域,那它将会从上一个域移除,绑定到当前域。

domain.remove(emitter)

  • emitter {EventEmitter | Timer} 要移除的分发器或计时器

与domain.add(emitter)函数恰恰相反,这个函数将分发器移除出域。

domain.bind(callback)

  • callback {Function} 回调函数
  • return: {Function}被绑定的函数

返回的函数是一个对于所提供的回调函数的包装函数。当调用这个返回的函数被时,所有被抛出的错误都会被导向到这个域的error事件。

Example

var d = domain.create();function readSomeFile(filename, cb) {  fs.readFile(filename, 'utf8', d.bind(function(er, data) {    // if this throws, it will also be passed to the domain    return cb(er, data ? JSON.parse(data) : null);  }));}d.on('error', function(er) {  // an error occurred somewhere.  // if we throw it now, it will crash the program  // with the normal line number and stack message.});

domain.intercept(callback)

  • callback {Function} 回调函数
  • return: {Function} 被拦截的函数

domain.bind(callback)类似。除了捕捉被抛出的错误外,它还会拦截Error对象作为参数传递到这个函数。

这种方式下,常见的if (er) return callback(er);模式,能被一个地方一个错误处理替换。

Example

var d = domain.create();function readSomeFile(filename, cb) {  fs.readFile(filename, 'utf8', d.intercept(function(data) {    // note, the first argument is never passed to the    // callback since it is assumed to be the 'Error' argument    // and thus intercepted by the domain.    // if this throws, it will also be passed to the domain    // so the error-handling logic can be moved to the 'error'    // event on the domain instead of being repeated throughout    // the program.    return cb(null, JSON.parse(data));  }));}d.on('error', function(er) {  // an error occurred somewhere.  // if we throw it now, it will crash the program  // with the normal line number and stack message.});

domain.enter()

这个函数就像runbindintercept的管道系统,它设置有效域。它设定了域的domain.activeprocess.domain,还隐式的将域推到域模块管理的域栈(关于域栈的细节详见domain.exit())。enter函数的调用,分隔了异步调用链以及绑定到一个域的I/O操作的结束或中断。

调用enter仅改变活动的域,而不改变域本身。在一个单独的域里可以调用任意多次Enterexit

domain.exit()

exit函数退出当前域,并从域的栈里移除。每当程序的执行流程要切换到不同的异步调用链的时候,要保证退出当前域。调用exit函数,分隔了异步调用链,和绑定到一个域的I/O操作的结束或中断。

如果有多个嵌套的域绑定到当前的上下文,exit函数将会退出所有嵌套。

调用exit仅改变活跃域,不会改变自身域。在一个单独的域里可以调用任意多次Enterexit

如果在这个域名下exit已经被设置,exit将不退出域返回。

domain.dispose()

稳定性: 0 - 抛弃。通过域里设置的错误事件来显示的消除失败的 IO 操作。

调用dispos后,通过run,bind或intercept绑定到域的回调函数不再使用这个域,并且分发dispose事件。


Stability: 3 - Stable

Node.js可以使用require('tls')来访问TLS/SSL模块:

const tls = require('tls');

tls模块使用OpenSSL来提供传输层安全性(Transport Layer Security,TLS)和安全套接层(Secure Socket Layer,SSL):加密过的流通讯。

TLS/SSL是一种公钥/私钥基础架构,每个客户端和服务端都需要一个私钥。私钥的创建方法如下:

openssl genrsa -out ryans-key.pem 2048

你还需要为所有服务器和某些客户端添加证书。证书由认证中心(Certificate Authority)签名,或者自签名。获得证书第一步是创建一个证书签名请求"Certificate Signing Request" (CSR)文件。证书的创建方法如下:

openssl req -new -sha256 -key ryans-key.pem -out ryans-csr.pem

使用CSR创建一个自签名的证书:

openssl x509 -req -in ryans-csr.pem -signkey ryans-key.pem -out ryans-cert.pem

或者你可以发送CSR给认证中心(Certificate Authority)来签名。

(TODO: 创建CA的文档,感兴趣的读者可以在Node源码test/fixtures/keys/Makefile里查看)

创建.pfx或.p12,可以这么做:

openssl pkcs12 -export -in agent5-cert.pem -inkey agent5-key.pem     -certfile ca-cert.pem -out agent5.pfx
  • in: 签名证书
  • inkey: 关联的私钥
  • certfile: 是将所有证书颁发机构 (CA) 证书连接到单个文件中,例如,cat ca1-cert.pem ca2-cert.pem > ca-cert.pem

协议支持

Node.js默认遵循SSLv2和SSLv3协议,不过这些协议被禁用。因为他们不太可靠,很容易受到威胁,参见CVE-2014-3566。某些情况下,旧版本客户端/服务器(比如 IE6)可能会产生问题。如果你想启用SSLv2或SSLv3 ,使用参数--enable-ssl2--enable-ssl3运行Node。Node.js的未来版本中不会再默认编译SSLv2 和SSLv3。

有一个办法可以强制node进入仅使用SSLv3或SSLv2模式,分别指定secureProtocol'SSLv3_method''SSLv2_method'

Node.js使用的默认协议方法准确名字是AutoNegotiate_method, 这个方法会尝试并协商客户端支持的从高到底协议。为了提供默认的安全级别,Node.js(v0.10.33 版本之后)通过将secureOptions设为SSL_OP_NO_SSLv3|SSL_OP_NO_SSLv2 ,明确的禁用了SSLv3和SSLv2(除非你给secureProtocol 传值--enable-ssl3,或--enable-ssl2,或SSLv3_method)。

如果你设置了secureOptions,我们不会重新这个参数。

改变这个行为的后果:

  • 如果你的应用被当做为安全服务器,SSLv3客户端不能协商建立连接,会被拒绝。这种情况下,你的服务器会触发clientError事件。错误消息会包含错误版本数字('wrong version number')。
  • 如果你的应用被当做安全客户端,和一个不支持比SSLv3更高安全性的方法的服务器通讯,你的连接不会协商成功。这种情况下,你的客户端会触发 clientError事件。错误消息会包含错误版本数字('wrong version number')。

Client-initiated renegotiation attack mitigation

TLS协议让客户端协商TLS会话的某些方法内容。但是,会话协商需要服务器端响应的资源,这回让它成为阻断服务攻击(denial-of-service attacks)的潜在媒介。

为了降低这种情况的发生,重新协商被限制为每10分钟3次。当超出这个界限时,在tls.TLSSocket实例上会触发错误。这个限制可设置:

  • tls.CLIENT_RENEG_LIMIT: 重新协商limit,默认是3。

  • tls.CLIENT_RENEG_WINDOW: 重新协商窗口的时间,单位秒,默认是10分钟。

除非你明确知道自己在干什么,否则不要改变默认值。

要测试你的服务器的话,使用openssl s_client -connect address:port连接服务器,并敲R<CR>(字母 R 键加回车)几次。

NPN 和 SNI

NPN(Next Protocol Negotiation 下次协议协商)和SNI (Server Name Indication 域名指示)都是TLS握手扩展:

  • NPN - 同一个TLS服务器使用多种协议 (HTTP, SPDY)
  • SNI - 同一个TLS服务器使用多个主机名(不同的 SSL 证书)。certificates.

完全正向保密

"Forward Secrecy"或"Perfect Forward Secrecy-完全正向保密"协议描述了秘钥协商(比如秘钥交换)方法的特点。实际上这意味着及时你的服务器的秘钥有危险,通讯仅有可能被一类人窃听,他们必须设法获的每次会话都会生成的秘钥对。

完全正向保密是通过每次握手时为秘钥协商随机生成密钥对来完成(和所有会话一个key相反)。实现这个技术(提供完全正向保密-Perfect Forward Secrecy)的方法被称为"ephemeral"。

通常目前有2个方法用于完成完全正向保密(Perfect Forward Secrecy):

  • DHE - 一个迪菲-赫尔曼密钥交换密钥协议(Diffie Hellman key-agreement protocol)短暂(ephemeral)版本。
  • ECDHE - 一个椭圆曲线密钥交换密钥协议( Elliptic Curve Diffie Hellman key-agreement protocol)短暂(ephemeral)版本。

短暂(ephemeral)方法有性能缺点,因为生成 key 非常耗费资源。

tls.getCiphers()

返回支持的SSL密码名数组。

例子:

var ciphers = tls.getCiphers();console.log(ciphers); // ['AES128-SHA', 'AES256-SHA', ...]

tls.createServer(options[, secureConnectionListener])

创建一个新的tls.Server。参数connectionListener会自动设置为secureConnection事件的监听器。参数options对象有以下可能性:

  • pfx: 包含私钥,证书和服务器的CA证书(PFX或PKCS12 格式)字符串或缓存Buffer。(keycertca互斥)。

  • key: 包含服务器私钥(PEM格式)字符串或缓存Buffer。(可以是keys的数组)(必传)。

  • passphrase: 私钥或pfx的密码字符串

  • cert: 包含服务器证书key(PEM格式)字符串或缓存Buffer。(可以是certs的数组)(必传)。

  • ca: 信任的证书(PEM格式)的字符串/缓存数组。如果忽略这个参数,将会使用"root" CAs ,比如VeriSign。用来授权连接。

  • crl : 不是PEM编码CRLs (证书撤销列表 Certificate Revocation List)的字符串就是字符串列表.

  • ciphers: 要使用或排除的密码(cipher)字符串

    为了减轻BEAST attacks ,推荐使用这个参数和之后会提到的honorCipherOrder参数来优化non-CBC密码(cipher)

    默认:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL。格式上更多细节参见OpenSSL cipher list format documentation

    ECDHE-RSA-AES128-SHA256, DHE-RSA-AES128-SHA256AES128-GCM-SHA256都是TLS v1.2密码(cipher),当node.js连接OpenSSL 1.0.1或更早版本(比如)时使用。注意,honorCipherOrder设置为enabled后,现在仍然可以和TLS v1.2客户端协商弱密码(cipher),

    RC4可作为客户端和老版本TLS协议通讯的备用方法。RC4这些年受到怀疑,任何对信任敏感的对象都会考虑其威胁性。国家级别(state-level)的参与者拥有中断它的能力。

    注意: 早些版本的修订建议,AES256-SHA作为可以接受的密码(cipher).Unfortunately,AES256-SHA是一个CBC密码(cipher),容易受到BEAST attacks 攻击,不要使用它。

  • ecdhCurve: 包含用来ECDH秘钥交换弧形(curve)名字符串,或者false禁用ECDH。

    默认prime256v1。更多细节参考RFC 4492

  • dhparam: DH参数文件,用于DHE秘钥协商。使用openssl dhparam命令来创建。如果加载文件失败,会悄悄的抛弃它。

  • handshakeTimeout: 如果SSL/TLS握手事件超过这个参数,会放弃里连接。默认是120秒.

    握手超时后,tls.Server对象会触发'clientError'事件。

  • honorCipherOrder: 当选择一个密码(cipher)时,使用服务器配置,而不是客户端的。

    虽然这个参数默认不可用,还是推荐你用这个参数,和ciphers参数连接使用,减轻BEAST攻击。

    注意,如果使用了SSLv2,服务器会发送自己的配置列表给客户端,客户端会挑选密码(cipher)。默认不支持SSLv2,除非node.js配置了./configure --with-sslv2

  • requestCert: 如果设为true,服务器会要求连接的客户端发送证书,并尝试验证证书。默认:false

  • rejectUnauthorized: 如果为true,服务器将会拒绝任何不被CAs列表授权的连接。仅requestCert参数为true时这个参数才有效。默认:false

  • checkServerIdentity(servername, cert): 提供一个重写的方法来检查证书对应的主机名。如果验证失败,返回error。如果验证通过,返回undefined

  • NPNProtocols: NPN协议的Buffer数组(协议需按优先级排序)。

  • SNICallback(servername, cb): 如果客户端支持SNI TLS扩展会调用这个函数。会传入2个参数:servernamecbSNICallback必须调用 cb(null, ctx),其中ctx是SecureContext实例。(你可以用tls.createSecureContext(...)来获取相应的SecureContext上下文)。如果 SNICallback没有提供,将会使用高级的API(参见下文).

  • sessionTimeout: 整数,设定了服务器创建TLS会话标示符(TLS session identifiers)和TLS会话票据(TLS session tickets)后的超时时间(单位:秒)。更多细节参见:SSL_CTX_set_timeout

  • ticketKeys: 一个48字节的Buffer实例,由16字节的前缀,16字节的hmac key,16字节的AES key组成。可用用它来接受tls服务器实例上的tls会话票据(tls session tickets)。

    注意: 自动在集群模块( cluster module)工作进程间共享。

  • sessionIdContext: 会话恢复(session resumption)的标示符字符串。如果requestCerttrue。默认值为命令行生成的MD5哈希值。否则不提供默认值。

  • secureProtocol: SSL使用的方法,例如,SSLv3_method强制SSL版本为3。可能的值定义于你所安装的OpenSSL中的常量SSL_METHODS

  • secureOptions: 设置服务器配置。例如设置SSL_OP_NO_SSLv3可用禁用SSLv3协议。所有可用的参数见SSL_CTX_set_options

响应服务器的简单例子:

var tls = require('tls');var fs = require('fs');var options = {  key: fs.readFileSync('server-key.pem'),  cert: fs.readFileSync('server-cert.pem'),  // This is necessary only if using the client certificate authentication.  requestCert: true,  // This is necessary only if the client uses the self-signed certificate.  ca: [ fs.readFileSync('client-cert.pem') ]};var server = tls.createServer(options, function(socket) {  console.log('server connected',              socket.authorized ? 'authorized' : 'unauthorized');  socket.write("welcome!
");  socket.setEncoding('utf8');  socket.pipe(socket);});server.listen(8000, function() {  console.log('server bound');});

或者:

var tls = require('tls');var fs = require('fs');var options = {  pfx: fs.readFileSync('server.pfx'),  // This is necessary only if using the client certificate authentication.  requestCert: true,};var server = tls.createServer(options, function(socket) {  console.log('server connected',              socket.authorized ? 'authorized' : 'unauthorized');  socket.write("welcome!
");  socket.setEncoding('utf8');  socket.pipe(socket);});server.listen(8000, function() {  console.log('server bound');});

你可以通过openssl s_client连接服务器来测试:

openssl s_client -connect 127.0.0.1:8000

tls.connect(options[, callback])

tls.connect(port[, host][, options][, callback])

创建一个新的客户端连接到指定的端口和主机(port and host)(老版本API),或者options.portoptions.host(如果忽略host,默认为 localhost)。options是一个包含以下值得对象:

  • host: 客户端需要连接到的主机。

  • port: 客户端需要连接到的端口。

  • socket: 在指定的socket(而非新建)上建立安全连接。如果这个参数有值,将忽略hostport参数。

  • path: 创建 到参数path的unix socket连接。如果这个参数有值,将忽略hostport参数。

  • pfx: 包含私钥,证书和客户端(PFX或PKCS12格式)的CA证书的字符串或Buffer缓存。

  • key: 包含客户端(PEM格式)的 私钥的字符串或Buffer缓存。可以是keys数组。

  • passphrase: 私钥或pfx的密码字符串。

  • cert: 包含客户端证书key(PEM格式)字符串或缓存Buffer。(可以是certs的数组)。

  • ca: 信任的证书(PEM格式)的字符串/缓存数组。如果忽略这个参数,将会使用"root" CAs ,比如VeriSign。用来授权连接。

  • rejectUnauthorized: 如果为true,服务器证书根据CAs列表授权列表验证。如果验证失败,触发'error'事件;err.code包含OpenSSL错误代码。默认:true

  • NPNProtocols: NPN协议的字符串或Buffer数组。Buffer必须有以下格式0x05hello0x05world,第一个字节是下一个协议名字的长度。(传的数组通常非常简单,比如:['hello', 'world'])。

  • servername: SNI(域名指示 Server Name Indication) TLS扩展的服务器名。

  • secureProtocol: SSL使用的方法,例如,SSLv3_method强制SSL版本为3。可能的值定义于你所安装的OpenSSL中的常量SSL_METHODS

  • session: 一个Buffer实例,包含TLS会话.

将参数callback添加到'secureConnect'事件上,其效果如同监听器。

tls.connect()返回一个tls.TLSSocket对象。

这是一个简单的客户端应答服务器例子:

var tls = require('tls');var fs = require('fs');var options = {  // These are necessary only if using the client certificate authentication  key: fs.readFileSync('client-key.pem'),  cert: fs.readFileSync('client-cert.pem'),  // This is necessary only if the server uses the self-signed certificate  ca: [ fs.readFileSync('server-cert.pem') ]};var socket = tls.connect(8000, options, function() {  console.log('client connected',              socket.authorized ? 'authorized' : 'unauthorized');  process.stdin.pipe(socket);  process.stdin.resume();});socket.setEncoding('utf8');socket.on('data', function(data) {  console.log(data);});socket.on('end', function() {  server.close();});

或者:

var tls = require('tls');var fs = require('fs');var options = {  pfx: fs.readFileSync('client.pfx')};var socket = tls.connect(8000, options, function() {  console.log('client connected',              socket.authorized ? 'authorized' : 'unauthorized');  process.stdin.pipe(socket);  process.stdin.resume();});socket.setEncoding('utf8');socket.on('data', function(data) {  console.log(data);});socket.on('end', function() {  server.close();});

类: tls.TLSSocket

net.Socket实例的封装,取代内部socket读写程序,执行透明的输入/输出数据的加密/解密。

new tls.TLSSocket(socket, options)

从现有的TCP socket里构造一个新的TLSSocket对象。

socket一个net.Socket的实例

options一个包含以下属性的对象:

  • secureContext: 来自tls.createSecureContext( ... )的可选TLS上下文对象。

  • isServer: 如果为true, TLS socket将会在服务器模式(server-mode)初始化。

  • server: 一个可选的net.Server实例

  • requestCert: 可选的,参见tls.createSecurePair

  • rejectUnauthorized: 可选的,参见tls.createSecurePair

  • NPNProtocols: 可选的,参见tls.createServer

  • SNICallback: 可选的,参见tls.createServer

  • session: 可选的,一个Buffer实例,包含TLS会话

  • requestOCSP: 可选的,如果为true- OCSP状态请求扩展将会被添加到客户端hello,并且OCSPResponse事件将会在socket上建立安全通讯前触发。

tls.createSecureContext(details)

创建一个凭证(credentials)对象,包含字典有以下的key:

  • pfx : 包含PFX或PKCS12加密的私钥,证书和服务器的CA证书(PFX或PKCS12格式)字符串或缓存Buffer。(keycertca互斥)。
  • key : 包含PEM加密过的私钥的字符串。
  • passphrase : 私钥或pfx的密码字符串。
  • cert : 包含PEM加密过的证书的字符串。
  • ca : 信任的PEM加密过的可信任的证书(PEM格式)字符串/缓存数组。
  • crl :PEM加密过的CRLs(证书撤销列表)
  • ciphers: 要使用或排除的密码(cipher)字符串。更多格式上的细节参见http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT
  • honorCipherOrder : 当选择一个密码(cipher) 时, 使用服务器配置,而不是客户端的。 更多细节参见tls模块文档。

如果没给 'ca' 细节,node.js 将会使用默认的公开信任的 CAs 列表(参见http://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt)。

tls.createSecurePair([context][, isServer][, requestCert][, rejectUnauthorized])

创建一个新的安全对(secure pair)对象,包含2个流,其中一个读/写加密过的数据,另外一个读/写明文数据。通常加密端数据来自是从输入的加密数据流,另一端被当做初始加密流。

  • credentials: 来自tls.createSecureContext( ... )的安全上下文对象。

  • isServer: 是否以服务器/客户端模式打开这个tls连接。

  • requestCert: 是否服务器需要连接的客户端发送证书。仅适用于服务端连接。

  • rejectUnauthorized:非法证书时,是否服务器需要自动拒绝客户端。启用requestCert后,才适用于服务器。

tls.createSecurePair()返回一个安全对(SecurePair)对象,包含明文cleartext和密文encrypted流 。

注意: cleartexttls.TLSSocket拥有相同的 API。

类: SecurePair

通过tls.createSecurePair返回。

事件: 'secure'

一旦安全对(SecurePair)成功建立一个安全连接,安全对(SecurePair)将会触发这个事件。

和检查服务器'secureConnection'事件一样,pair.cleartext.authorized必须检查确认是否适用的证书是授权过的。

类: tls.Server

这是net.Server的子类,拥有相同的方法。这个类接受适用TLS或SSL的加密连接,而不是接受原始TCP连接。

事件: 'secureConnection'

function (tlsSocket) {}

新的连接握手成功后回触发这个事件。参数是tls.TLSSocket实例。它拥有常用的流方法和事件。

socket.authorized是否客户端被证书(服务器提供)授权。如果socket.authorized为false,socket.authorizationError是如何授权失败。值得一提的是,依赖于TLS服务器的设置,你的未授权连接可能也会被接受。socket.authorizationError如何授权失败。值得一提的是,依赖于TLS服务器的设置,你的未授权连接可能也会被接受。socket.npnProtocol包含选择的NPN协议的字符串。socket.servername包含SNI请求的服务器名的字符串。

事件: 'clientError'

function (exception, tlsSocket) { }

在安全连接建立前,客户端连接触发'error'事件会转发到这里来。

tlsSockettls.TLSSocket,错误是从这里触发的。

事件: 'newSession'

function (sessionId, sessionData, callback) { }

创建TLS会话的时候会触发。可能用来在外部存储器里存储会话。callback必须最后调用,否则没法从安全连接发送/接收数据。

注意: 添加这个事件监听器仅会在连接连接时有效果。

事件: 'resumeSession'

function (sessionId, callback) { }

当客户端想恢复之前的TLS会话时会触发。事件监听器可能会使用sessionId到外部存储器里查找,一旦结束会触发callback(null, sessionData)。如果会话不能恢复(比如不存在这个会话),可能会调用callback(null, null)。调用callback(err)将会终止连接,并销毁socket。

注意: 添加这个事件监听器仅会在连接连接时有效果。

事件: 'OCSPRequest'

function (certificate, issuer, callback) { }

当客户端发送证书状态请求时会触发。你可以解析服务器当前的证书,来获取OCSP网址和证书id,获取OCSP响应调用callback(null, resp),其中respBuffer实例。证书(certificate)和发行者(issuer)都是初级表达式缓存(BufferDER-representations of the primary)和证书的发行者。它可以用来获取OCSP证书和OCSP终点网址。

可以调用callback(null, null),表示没有OCSP响应。

调用callback(err)可能会导致调用socket.destroy(err)

典型流程:

  1. 客户端连接服务器,并发送OCSPRequest(通过 ClientHello 里的状态信息扩展)。
  2. 服务器收到请求,调用OCSPRequest事件监听器。
  3. 服务器从certificateissuer获取OCSP网址,并执行OCSP request到CA
  4. 服务器CA收到OCSPResponse, 并通过callback参数送回到客户端
  5. 客户端验证响应,并销毁socket或执行握手

注意: 如果证书是自签名的,或者如果发行者不再根证书列表里(你可以通过参数提供一个发行者)。issuer就可能为null。

注意:添加这个事件监听器仅会在连接连接时有效果。

注意:你可以能想要使用npm模块(比如asn1.js)来解析证书。

server.listen(port[, host][, callback])

在指定的端口和主机上开始接收连接。如果host参数没传,服务接受通过IPv4地址(INADDR_ANY)的直连。

这是异步函数。当服务器已经绑定后回调用最后一个参数callback

更多信息参见net.Server

server.close()

停止服务器,不再接收新连接。这是异步函数,当服务器触发'close'事件后回最终关闭。

server.address()

返回绑定的地址,地址家族名和服务器端口。更多信息参见net.Server.address()

server.addContext(hostname, context)

如果客户端请求SNI主机名和传入的hostname相匹配,将会用到安全上下文(secure context)。context可以包含keycertca和/或tls.createSecureContextoptions参数的其他任何属性。

server.maxConnections

设置这个属性可以在服务器的连接数达到最大值时拒绝连接。

server.connections

当前服务器连接数。

类: CryptoStream

稳定性: 0 - 抛弃. 使用 tls.TLSSocket 替代.

这是一个加密的流

cryptoStream.bytesWritten

底层socket写字节访问器(bytesWritten accessor)的代理,将会返回写到socket的全部字节数。包括 TLS 的开销。

类: tls.TLSSocket

net.Socket实例的封装,透明的加密写数据和所有必须的TLS协商。

这个接口实现了一个双工流接口。它包含所有常用的流方法和事件。

事件: 'secureConnect'

新的连接成功握手后回触发这个事件。无论服务器证书是否授权,都会调用监听器。用于用户测试tlsSocket.authorized看看如果服务器证书已经被指定的CAs签名。如果tlsSocket.authorized === false ,可以在tlsSocket.authorizationError里找到错误。如果使用了NPN,你可以检tlsSocket.npnProtocol获取协商协议(negotiated protocol)。

事件: 'OCSPResponse'

function (response) { }

如果启用requestOCSP参赛会触发这个事件。response是缓存对象,包含服务器的OCSP响应。

一般来说,response是服务器CA签名的对象,它包含服务器撤销证书状态的信息。

tlsSocket.encrypted

静态boolean变量,一直是true。可以用来区别TLS socket和常规对象。

tlsSocket.authorized

boolean变量,如果对等实体证书(peer's certificate)被指定的某个CAs签名,返回true,否则false

tlsSocket.authorizationError

对等实体证书(peer's certificate)没有验证通过的原因。当tlsSocket.authorized === false时,这个属性才可用。

tlsSocket.getPeerCertificate([ detailed ])

返回一个代表对等实体证书( peer's certificate)的对象。这个返回对象有一些属性和证书内容相对应。如果参数detailedtrue,将会返回包含发行者issuer完整链。如果false,仅有顶级证书没有发行者issuer属性。

例子:

{ subject:   { C: 'UK',     ST: 'Acknack Ltd',     L: 'Rhys Jones',     O: 'node.js',     OU: 'Test TLS Certificate',     CN: 'localhost' },  issuerInfo:   { C: 'UK',     ST: 'Acknack Ltd',     L: 'Rhys Jones',     O: 'node.js',     OU: 'Test TLS Certificate',     CN: 'localhost' },  issuer:   { ... another certificate ... },  raw: < RAW DER buffer >,  valid_from: 'Nov 11 09:52:22 2009 GMT',  valid_to: 'Nov  6 09:52:22 2029 GMT',  fingerprint: '2A:7A:C2:DD:E5:F9:CC:53:72:35:99:7A:02:5A:71:38:52:EC:8A:DF',  serialNumber: 'B9B0D332A1AA5635' }

如果peer没有提供证书,返回null或空对象。

tlsSocket.getCipher()

返回一个对象,它代表了密码名和当前连接的SSL/TLS协议的版本。

例子:{ name: 'AES256-SHA', version: 'TLSv1/SSLv3' }

更多信息参见http://www.openssl.org/docs/ssl/ssl.html#DEALING_WITH_CIPHERS里的SSL_CIPHER_get_name()和SSL_CIPHER_get_version() 。

tlsSocket.renegotiate(options, callback)

初始化 TLS 重新协商进程。参数options可能包含以下内容:rejectUnauthorizedrequestCert(细节参见tls.createServer)。一旦重新协商成(renegotiation)功完成,将会执行callback(err),其中errnull

注意:当安全连接建立后,可以用这来请求对等实体证书(peer's certificate)。

注意:作为服务器运行时,handshakeTimeout超时后,socket将会被销毁。

tlsSocket.setMaxSendFragment(size)

设置最大的TLS碎片大小(默认最大值为:16384,最小值为:512)。成功的话,返回true,否则返回false

小的碎片包会减少客户端的缓存延迟:大的碎片直到接收完毕后才能被TLS层完全缓存,并且验证过完整性;大的碎片可能会有多次往返,并且可能会因为丢包或重新排序导致延迟。而小的碎片会增加额外的TLS帧字节和CPU负载,这会减少CPU的吞吐量。

tlsSocket.getSession()

返回ASN.1编码的TLS会话,如果没有协商,会返回。连接到服务器时,可以用来加速握手的建立。

tlsSocket.getTLSTicket()

注意:仅和客户端TLS socket打交道。仅在调试时有用,会话重用是,提供session参数给tls.connect

返回TLS会话票据(ticket),或如果没有协商(negotiated),返回undefined

tlsSocket.address()

返回绑定的地址,地址家族名和服务器端口。更多信息参见net.Server.address()。返回三个属性, 比如:{ port: 12346, family: 'IPv4', address: '127.0.0.1' }

tlsSocket.remoteAddress

表示远程IP地址(字符串表示),例如:'74.125.127.100''2001:4860:a005::68'.

tlsSocket.remoteFamily

表示远程 IP 家族,'IPv4''IPv6'

tlsSocket.remotePort

远程端口(数字表示),例如,443

tlsSocket.localAddress

本地IP地址(字符串表示)。

tlsSocket.localPort

本地端口号(数字表示)。


进程

本节介绍Node.js的process(过程)对象,它提供有关当前Node.js过程的信息和控制。

process是全局对象,能够在任意位置对其进行访问,而无需使用require(),是EventEmitter的实例。

退出状态码

当不需要处理新的异步的操作时,Node正常情况下退出时会返回状态码0。下面提供了一些表示其他状态的状态码:

  • 1未捕获的致命异常-Uncaught Fatal Exception - 有未捕获异常,并且没有被域或 uncaughtException 处理函数处理。
  • 2- Unused (保留)
  • 3JavaScript解析错误-Internal JavaScript Parse Error - JavaScript的源码启动 Node 进程时引起解析错误。非常罕见,仅会在开发Node时才会有。
  • 4JavaScript评估失败-Internal JavaScript Evaluation Failure - JavaScript的源码启动Node进程,评估时返回函数失败。非常罕见,仅会在开发 Node时才会有。
  • 5致命错误-Fatal Error - V8里致命的不可恢复的错误。通常会打印到stderr,内容为:FATAL ERROR
  • 6Non-function异常处理-Non-function Internal Exception Handler - 未捕获异常,内部异常处理函数不知为何设置为on-function,并且不能被调用。
  • 7异常处理函数运行时失败-Internal Exception Handler Run-Time Failure - 未捕获的异常, 并且异常处理函数处理时自己抛出了异常。例如,如果 process.on('uncaughtException')domain.on('error')抛出了异常。
  • 8- Unused保留。 之前版本的Node, 状态码8有时表示未捕获异常。
  • 9- 参数非法-Invalid Argument - 可能是给了未知的参数,或者给的参数没有值。
  • 10运行时失败-Internal JavaScript Run-Time Failure - JavaScript的源码启动 Node 进程时抛出错误,非常罕见,仅会在开发 Node 时才会有。
  • 12无效的Debug参数-Invalid Debug Argument - 设置了参数--debug--debug-brk,但是选择了错误端口。
  • >128信号退出-Signal Exits - 如果Node接收到致命信号,比如SIGKILLSIGHUP,那么退出代码就是128加信号代码。这是标准的Unix做法,退出信号代码放在高位。

事件: 'exit'

当进程准备退出时触发。此时已经没有办法阻止从事件循环中推出。因此,你必须在处理函数中执行同步操作。这是一个在固定事件检查模块状态(比如单元测试)的好时机。回调函数有一个参数,它是进程的退出代码。

监听exit事件的例子:

process.on('exit', function(code) {  // do *NOT* do this  setTimeout(function() {    console.log('This will not run');  }, 0);  console.log('About to exit with code:', code);});

事件: 'beforeExit'

当node清空事件循环,并且没有其他安排时触发这个事件。通常来说,当没有进程安排时node退出,但是'beforeExit'的监听器可以异步调用,这样node就会继续执行。

'beforeExit'并不是明确退出的条件,process.exit()或异常捕获才是,所以不要把它当做'exit'事件。除非你想安排更多的工作。

事件: 'uncaughtException'

当一个异常冒泡回到事件循环,触发这个事件。如果给异常添加了监视器,默认的操作(打印堆栈跟踪信息并退出)就不会发生。

监听uncaughtException的例子:

process.on('uncaughtException', function(err) {  console.log('Caught exception: ' + err);});setTimeout(function() {  console.log('This will still run.');}, 500);// Intentionally cause an exception, but don't catch it.nonexistentFunc();console.log('This will not run.');

注意:uncaughtException是非常简略的异常处理机制。

尽量不要使用它,而应该用domains 。如果你用了,每次未处理异常后,重启你的程序。

不要使用node.js里诸如On Error Resume Next这样操作。每个未处理的异常意味着你的程序,和你的node.js扩展程序,一个未知状态。盲目的恢复意味着任何事情都可能发生

你在升级的系统时拉掉了电源线,然后恢复了。可能10次里有9次没有问题,但是第10次,你的系统可能就会挂掉。

Signal 事件

当进程接收到信号时就触发。信号列表详见标准的POSIX信号名,如SIGINT、SIGUSR1等。

监听SIGINT的例子:

// Start reading from stdin so we don't exit.process.stdin.resume();process.on('SIGINT', function() {  console.log('Got SIGINT.  Press Control-D to exit.');});

在大多数终端程序里,发送SIGINT信号的简单方法是按下信号Control-C

注意:

  • SIGUSR1node.js 接收这个信号开启调试模式。可以安装一个监听器,但开始时不会中断调试。
  • SIGTERMSIGINT在非Windows系统里,有默认的处理函数,退出(伴随退出代码128 + 信号码)前,重置退出模式。如果这些信号有监视器,默认的行为将会被移除。
  • SIGPIPE默认情况下忽略,可以加监听器。
  • SIGHUP当Windowns控制台关闭的时候生成,其他平台的类似条件,参见signal(7)。可以添加监听者,Windows平台上10秒后会无条件退出。在非Windows平台上,SIGHUP的默认操作是终止node,但是一旦添加了监听器,默认动作将会被移除。
  • SIGTERMWindows不支持,可以被监听。
  • SIGINT所有的终端都支持,通常由CTRL+C生成(可能需要配置)。当终端原始模式启用后不会再生成。
  • SIGBREAKWindows里,按下CTRL+BREAK会发送。非Windows平台,可以被监听,但是不能发送或生成。
  • SIGWINCH- 当控制台被重设大小时发送。Windows系统里,仅会在控制台上输入内容时,光标移动,或者可读的tty在原始模式上使用。
  • SIGKILL不能有监视器,在所有平台上无条件关闭node。
  • SIGSTOP不能有监视器。

Windows不支持发送信号,但是node提供了很多process.kill()child_process.kill()的模拟:

  • 发送Sending信号0可以查找运行中得进程
  • 发送SIGINTSIGTERMSIGKILL会引起目标进程无条件退出。

process.stdout

一个Writable Stream执向stdout(fd1).

例如:console.log的定义:

console.log = function(d) {  process.stdout.write(d + '
');};

process.stderrprocess.stdout和 node 里的其他流不同,他们不会被关闭(end()将会被抛出),它们不会触发finish事件,并且写是阻塞的。

  • 引用指向常规文件或TTY文件描述符时,是阻塞的。
  • 引用指向pipe管道时:
    • 在Linux/Unix里阻塞.
    • 在Windows像其他流一样,不被阻塞

检查Node是否运行在TTY上下文中,从process.stderrprocess.stdoutprocess.stdin里读取isTTY属性。

$ node -p "Boolean(process.stdin.isTTY)"true$ echo "foo" | node -p "Boolean(process.stdin.isTTY)"false$ node -p "Boolean(process.stdout.isTTY)"true$ node -p "Boolean(process.stdout.isTTY)" | catfalse

更多信息参见the tty docs

process.stderr

一个指向stderr (fd2)的可写流。

process.stderrprocess.stdout和node里的其他流不同,他们不会被关闭(end()将会被抛出),它们不会触发finish事件,并且写是阻塞的。

  • 引用指向常规文件或TTY文件描述符时,是阻塞的。
  • 引用指向pipe管道时:
    • 在Linux/Unix里阻塞
    • 在Windows像其他流一样,不被阻塞

process.stdin

一个指向stdin (fd0)的可读流。

以下例子:打开标准输入流,并监听两个事件:

process.stdin.setEncoding('utf8');process.stdin.on('readable', function() {  var chunk = process.stdin.read();  if (chunk !== null) {    process.stdout.write('data: ' + chunk);  }});process.stdin.on('end', function() {  process.stdout.write('end');});

process.stdin可以工作在老模式里,和v0.10之前版本的node代码兼容。

更多信息参见Stream compatibility.

在老的流模式里,stdin流默认暂停,必须调用process.stdin.resume()读取。可以调用process.stdin.resume()切换到老的模式。

如果开始一个新的工程,最好选择新的流,而不是用老的流。

process.argv

包含命令行参数的数组。第一个元素是'node',第二个参数是JavaScript文件的名字,第三个参数是任意的命令行参数。

// print process.argvprocess.argv.forEach(function(val, index, array) {  console.log(index + ': ' + val);});

将会生成:

$ node process-2.js one two=three four0: node1: /Users/mjr/work/node/process-2.js2: one3: two=three4: four

process.execPath

开启当前进程的执行文件的绝对路径。

例子:

/usr/local/bin/node

process.execArgv

启动进程所需的 node 命令行参数。这些参数不会在process.argv里出现,并且不包含node执行文件的名字,或者任何在名字之后的参数。这些用来生成子进程,使之拥有和父进程有相同的参数。

例子:

$ node --harmony script.js --version

process.execArgv 的参数:

['--harmony']

process.argv 的参数:

['/usr/local/bin/node', 'script.js', '--version']

process.abort()

这将导致node触发abort事件。会让node退出并生成一个核心文件。

process.chdir(directory)

改变当前工作进程的目录,如果操作失败抛出异常。

console.log('Starting directory: ' + process.cwd());try {  process.chdir('/tmp');  console.log('New directory: ' + process.cwd());}catch (err) {  console.log('chdir: ' + err);}

process.cwd()

返回当前进程的工作目录:

console.log('Current directory: ' + process.cwd());

process.env

包含用户环境的对象,参见environ(7).

这个对象的例子:

{ TERM: 'xterm-256color',  SHELL: '/usr/local/bin/bash',  USER: 'maciej',  PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin',  PWD: '/Users/maciej',  EDITOR: 'vim',  SHLVL: '1',  HOME: '/Users/maciej',  LOGNAME: 'maciej',  _: '/usr/local/bin/node' }

你可以写入这个对象,但是不会改变当前运行的进程。以下的命令不会成功:

node -e 'process.env.foo = "bar"' && echo $foo

这个会成功:

process.env.foo = 'bar';console.log(process.env.foo);

process.exit([code])

使用指定的code结束进程。如果忽略,将会使用code 0

使用失败的代码退出:

process.exit(1);

Shell将会看到退出代码为1.

process.exitCode

进程退出时的代码,如果进程优雅的退出,或者通过process.exit()退出,不需要指定退出码。

设定process.exit(code)将会重写之前设置的process.exitCode

process.getgid()

注意:这个函数仅在POSIX平台上可用(例如,非Windows和Android)。

获取进程的群组标识(参见getgid(2))。获取到得时群组的数字id,而不是名字。

if (process.getgid) {  console.log('Current gid: ' + process.getgid());}

process.setgid(id)

注意:这个函数仅在POSIX平台上可用(例如,非Windows 和 Android)。

设置进程的群组标识(参见setgid(2))。可以接收数字ID或者群组名。如果指定了群组名,会阻塞等待解析为数字ID 。

if (process.getgid && process.setgid) {  console.log('Current gid: ' + process.getgid());  try {    process.setgid(501);    console.log('New gid: ' + process.getgid());  }  catch (err) {    console.log('Failed to set gid: ' + err);  }}

process.getuid()

注意:这个函数仅在POSIX平台上可用(例如,非Windows和Android)。

获取进程的用户标识(参见getuid(2))。这是数字的用户id,不是用户名:

if (process.getuid) {  console.log('Current uid: ' + process.getuid());}

process.setuid(id)

注意:这个函数仅在POSIX平台上可用(例如,非Windows和Android)。

设置进程的用户标识(参见setuid(2))。接收数字ID或字符串名字。果指定了群组名,会阻塞等待解析为数字ID。

if (process.getuid && process.setuid) {  console.log('Current uid: ' + process.getuid());  try {    process.setuid(501);    console.log('New uid: ' + process.getuid());  }  catch (err) {    console.log('Failed to set uid: ' + err);  }}

process.getgroups()

注意:这个函数仅在POSIX平台上可用(例如,非Windows和Android)。

返回进程的群组iD数组。POSIX系统没有保证一定有,但是node.js保证有。

process.setgroups(groups)

注意:这个函数仅在POSIX平台上可用(例如,非Windows和Android)。

设置进程的群组ID。这是授权操作,所有你需要有root权限,或者有CAP_SETGID能力。

列表可以包含群组IDs,群组名,或者两者都有。

process.initgroups(user, extra_group)

注意:这个函数仅在POSIX平台上可用(例如,非Windows和Android)。

读取/etc/group ,并初始化群组访问列表,使用成员所在的所有群组。这是授权操作,所有你需要有root权限,或者有CAP_SETGID能力。

user是用户名或者用户ID,extra_group是群组名或群组ID。

当你在注销权限 (dropping privileges) 的时候需要注意. 例子:

console.log(process.getgroups());         // [ 0 ]process.initgroups('bnoordhuis', 1000);   // switch userconsole.log(process.getgroups());         // [ 27, 30, 46, 1000, 0 ]process.setgid(1000);                     // drop root gidconsole.log(process.getgroups());         // [ 27, 30, 46, 1000 ]

process.version

一个编译属性,包含NODE_VERSION

console.log('Version: ' + process.version);

process.versions

一个属性,包含了node的版本和依赖。

console.log(process.versions);

打印出来:

{ http_parser: '1.0',  node: '0.10.4',  v8: '3.14.5.8',  ares: '1.9.0-DEV',  uv: '0.10.3',  zlib: '1.2.3',  modules: '11',  openssl: '1.0.1e' }

process.config

一个包含用来编译当前node执行文件的javascript配置选项的对象。它与运行./configure 脚本生成的"config.gypi"文件相同。

一个可能的输出:

{ target_defaults:   { cflags: [],     default_configuration: 'Release',     defines: [],     include_dirs: [],     libraries: [] },  variables:   { host_arch: 'x64',     node_install_npm: 'true',     node_prefix: '',     node_shared_cares: 'false',     node_shared_http_parser: 'false',     node_shared_libuv: 'false',     node_shared_v8: 'false',     node_shared_zlib: 'false',     node_use_dtrace: 'false',     node_use_openssl: 'true',     node_shared_openssl: 'false',     strict_aliasing: 'true',     target_arch: 'x64',     v8_use_snapshot: 'true' } }

process.kill(pid[, signal])

发送信号给进程。pid是进程id,并且signal是发送的信号的字符串描述。信号名是字符串,比如'SIGINT'或'SIGHUP'。如果忽略,信号会是'SIGTERM'。更多信息参见Signal 事件和kill(2) .

如果进程没有退出,会抛出错误。信号0可以用来测试进程是否存在。

注意,虽然这个这个函数名叫process.kill,它真的仅是信号发射器,就像kill系统调用。信号发射可以做其他事情,不仅是杀死目标进程。

例子, 给自己发信号:

process.on('SIGHUP', function() {  console.log('Got SIGHUP signal.');});setTimeout(function() {  console.log('Exiting.');  process.exit(0);}, 100);process.kill(process.pid, 'SIGHUP');

注意:当Node.js接收到SIGUSR1信号,它会开启debugger调试模式, 参见Signal Events.

process.pid

当前进程的PID:

console.log('This process is pid ' + process.pid);

process.title

获取/设置(Getter/setter) 'ps'中显示的进程名。

使用setter时,字符串的长度由系统指定,可能会很短。

在Linux和OS X上,它受限于名称的长度加上命令行参数的长度,因为它会覆盖参数内存(argv memory)。

v0.8版本允许更长的进程标题字符串,也支持覆盖环境内存,但是存在潜在的不安全和混乱(很难说清楚)。

process.arch

当前CPU的架构:'arm'、'ia32'或者'x64'。

console.log('This processor architecture is ' + process.arch);

process.platform

运行程序所在的平台系统'darwin''freebsd''linux''sunos'或者'win32'

console.log('This platform is ' + process.platform);

process.memoryUsage()

返回一个对象,描述了Node进程所用的内存状况,单位为字节。

var util = require('util');console.log(util.inspect(process.memoryUsage()));

将会生成:

{ rss: 4935680,  heapTotal: 1826816,  heapUsed: 650472 }

heapTotalheapUsed指V8的内存使用情况。

process.nextTick(callback)

  • callback{Function}

一旦当前事件循环结束,调用回到函数。

这不是setTimeout(fn, 0)的简单别名,这个效率更高。在任何附加I/O事件在子队列事件循环中触发前,它就会运行。

console.log('start');process.nextTick(function() {  console.log('nextTick callback');});console.log('scheduled');// Output:// start// scheduled// nextTick callback

在对象构造后,在I/O事件发生前,你又想改变附加事件处理函数时,这个非常有用。

function MyThing(options) {  this.setupOptions(options);  process.nextTick(function() {    this.startDoingStuff();  }.bind(this));}var thing = new MyThing();thing.getReadyForStuff();// thing.startDoingStuff() gets called now, not before.

要保证你的函数一定是100%同步执行,或者100%异步执行。例子:

// WARNING!  DO NOT USE!  BAD UNSAFE HAZARD!function maybeSync(arg, cb) {  if (arg) {    cb();    return;  }  fs.stat('file', cb);}

这个API非常危险. 如果你这么做:

maybeSync(true, function() {  foo();});bar();

不清楚foo()bar()哪个先执行。

更好的方法:

function definitelyAsync(arg, cb) {  if (arg) {    process.nextTick(cb);    return;  }  fs.stat('file', cb);}

注意:nextTick队列会在完全执行完毕之后才调用I/O操作。因此,递归设置nextTick的回调就像一个while(true);循环一样,将会阻止任何I/O操作的发生。

process.umask([mask])

设置或读取进程文件的掩码。子进程从父进程继承掩码。如果mask参数有效,返回旧的掩码。否则,返回当前掩码。

var oldmask, newmask = 0022;oldmask = process.umask(newmask);console.log('Changed umask from: ' + oldmask.toString(8) +            ' to ' + newmask.toString(8));

process.uptime()

返回Node已经运行的秒数。

process.hrtime()

返回当前进程的高分辨时间,形式为[seconds, nanoseconds]数组。它是相对于过去的任意事件。该值与日期无关,因此不受时钟漂移的影响。主要用途是可以通过精确的时间间隔,来衡量程序的性能。

你可以将之前的结果传递给当前的process.hrtime(),会返回两者间的时间差,用来基准和测量时间间隔。

var time = process.hrtime();// [ 1800216, 25 ]setTimeout(function() {  var diff = process.hrtime(time);  // [ 1, 552 ]  console.log('benchmark took %d nanoseconds', diff[0] * 1e9 + diff[1]);  // benchmark took 1000000527 nanoseconds}, 1000);

process.mainModule

require.main的备选方法。不同点,如果主模块在运行时改变,require.main可能会继续返回老的模块。可以认为,这两者引用了同一个模块。

process.mainModule属性提供了一种替代的检索方式require.main。不同之处在于,如果主模块在运行时发生更改,require.main可能仍会引用发生更改之前所需模块中的原始主模块。通常,假设这两个参照相同的模块是安全的。

require.main一样, 如果没有入口脚本,将会返回undefined

稳定性: 2 - 不稳定

Node.js的tty模块包含tty.ReadStreamtty.WriteStream类,多数情况下,你不必直接使用这个模块,访问该模块的方法如下:

const tty = require('tty');

当node检测到自己正运行于TTY上下文时,process.stdin将会是一个tty.ReadStream实例,并且process.stdout将会是tty.WriteStream实例。检测 node是否运行在TTY上下文的好方法是检测process.stdout.isTTY

$ node -p -e "Boolean(process.stdout.isTTY)"true$ node -p -e "Boolean(process.stdout.isTTY)" | catfalse

tty.isatty(fd)

如果fd和终端相关联返回true,否则返回false

tty.setRawMode(mode)

已经抛弃。使用tty.ReadStream#setRawMode()(比如process.stdin.setRawMode())替换。

Class: ReadStream

net.Socket的子类,表示tty的可读部分。通常情况,在任何node程序里(仅当isatty(0)为true时),process.stdintty.ReadStream的唯一实例。

rs.isRaw

Boolean值,默认为false。它代表当前tty.ReadStream实例的"raw"状态。

rs.setRawMode(mode)

mode需是truefalse。它设定tty.ReadStream属性为原始设备或默认。isRaw将会设置为结果模式。

Class: WriteStream

net.Socket的子类,代表tty的可写部分。通常情况下,process.stdouttty.WriteStream唯一实例(仅当isatty(1)为true时)。

ws.columns

TTY当前拥有的列数。触发"resize"事件时会更新这个值。

ws.rows

TTY当前拥有的行数。触发"resize"事件时会更新这个值。

Event: 'resize'

function () {}

行或列变化时会触发refreshSize()事件。

process.stdout.on('resize', function() {  console.log('screen size has changed!');  console.log(process.stdout.columns + 'x' + process.stdout.rows);});


稳定性: 3 - 稳定

Node.js的dgram模块提供了UDP数据报套接字的实现。

使用数据报文sockets(Datagram sockets)的方式是调用require('dgram')

重要提醒:dgram.Socket#bind()的行为在v0.10做了改动 ,它总是异步的。如果你的代码像下面的一样:

var s = dgram.createSocket('udp4');s.bind(1234);s.addMembership('224.0.0.114');

现在需要改为:

var s = dgram.createSocket('udp4');s.bind(1234, function() {  s.addMembership('224.0.0.114');});

dgram.createSocket(type[, callback])

  • type字符串。 'udp4'或'udp6'
  • callback函数。附加到message事件的监听器。可选参数。
  • 返回:Socket对象

创建指定类型的数据报文(datagram) Socket。有效类型是udp4udp6

接受一个可选的回调,会被添加为message的监听事件。

如果你想接收数据报文(datagram)可以调用socket.bind()socket.bind()将会绑定到所有接口("all interfaces")的随机端口上(udp4udp6 sockets都适用)。你可以通过socket.address().addresssocket.address().port获取地址和端口。

dgram.createSocket(options[, callback])

  • options对象
  • callback函数。给message事件添加事件监听器。
  • 返回:Socket对象

参数options必须包含type值(udp4udp6),或可选的boolean值reuseAddr

reuseAddr为 true 时,socket.bind()将会重用地址,即使另一个进程已经绑定socket。reuseAddr默认为false

回调函数为可选参数,作为message事件的监听器。

如果你想接受数据报文(datagram),可以调用socket.bind()socket.bind()将会绑定到所有接口("all interfaces")地址的随机端口上(udp4udp6 sockets都适用)。你可以通过socket.address().addresssocket.address().port获取地址和端口。

Class: dgram.Socket

报文数据Socket类封装了数据报文(datagram)函数。必须通过dgram.createSocket(...)函数创建。

Event: 'message'

  • msg缓存对象. 消息。
  • rinfo对象. 远程地址信息。

当socket上新的数据报文(datagram)可用的时候,会触发这个事件。msg是一个缓存,rinfo是一个包含发送者地址信息的对象。

socket.on('message', function(msg, rinfo) {  console.log('Received %d bytes from %s:%d
',              msg.length, rinfo.address, rinfo.port);});

Event: 'listening'

当socket开始监听数据报文(datagram)时触发。在UDP socket创建时触发。

Event: 'close'

当socket使用close()关闭时触发。在这个socket上不会触发新的消息事件。

Event: 'error'

  • exceptionError对象

当发生错误时触发。

socket.send(buf, offset, length, port, address[, callback])

  • buf缓存对象或字符串. 要发送的消息。
  • offset整数。消息在缓存中得偏移量。
  • length整数。消息的比特数。
  • port整数。端口的描述。
  • address字符串。目标的主机名或IP地址。
  • callback函数。当消息发送完毕的时候调用。可选。

对于UDP socket,必须指定目标端口和地址。address参数可能是字符串,它会被DNS解析。

如果忽略地址或者地址是空字符串,将使用'0.0.0.0''::0'替代。依赖于网络配置,这些默认值有可能行也可能不行。

如果socket之前没被调用bind绑定,则它会被分配一个随机端口并绑定到所有接口("all interfaces")地址(udp4sockets的'0.0.0.0' ,udp6sockets的'::0')

回调函数可能用来检测DNS错误,或用来确定什么时候重用buf对象。注意,DNS查询会导致发送tick延迟。通过回调函数能确认数据报文(datagram)是否已经发送的。

考虑到多字节字符串情况,偏移量和长度是字节长度byte length,而不是字符串长度。

下面的例子是在localhost上发送一个UDP包给随机端口:

var dgram = require('dgram');var message = new Buffer("Some bytes");var client = dgram.createSocket("udp4");client.send(message, 0, message.length, 41234, "localhost", function(err) {  client.close();});

关于UDP数据报文(datagram) 尺寸

IPv4/v6数据报文(datagram)的最大长度依赖于MTU (Maximum Transmission Unit)和Payload Length的长度。

  • Payload Length内容为16位宽,它意味着Payload的最大字节说不超过64k,其中包括了头信息和数据(65,507字节 = 65,535 − 8字节UDP头 − 20字节IP 头);对于环回接口(loopback interfaces)这是真的,但对于多数主机和网络来说不太现实。

  • MTU能支持数据报文(datagram)的最大值(以目前链路层技术来说)。对于任何连接,IPv4允许的最小值为68MTU,推荐值为576(通常推荐作拨号应用的MTU),无论他们是完整接收还是碎片接收。

    对于IPv6MTU的最小值为1280字节,最小碎片缓存大小为1500字节。16字节实在是太小,所以目前链路层一般最小MTU大小为1500

我们不可能知道一个包可能进过的每个连接的MTU。通常发送一个超过接收端MTU大小的数据报文(datagram)会失效。(数据包会被悄悄的抛弃,不会通知发送端数据包没有到达接收端)。

socket.bind(port[, address][, callback])

  • port整数
  • address字符串,可选
  • callback没有参数的函数,可选。绑定时会调用回调。

对于UDP socket,在一个端口和可选地址上监听数据报文(datagram)。如果没有指定地点,系统将会参数监听所有的地址。绑定完毕后,会触发"listening" 事件,并会调用传入的回调函数。指定监听事件和回调函数非常有用。

一个绑定了的数据报文socket会保持node进程运行来接收数据。

如果绑定失败,会产生错误事件。极少数情况(比如绑定一个关闭的socket)。这个方法会抛出一个错误。

以下是UDP服务器监听端口41234的例子:

var dgram = require("dgram");var server = dgram.createSocket("udp4");server.on("error", function (err) {  console.log("server error:
" + err.stack);  server.close();});server.on("message", function (msg, rinfo) {  console.log("server got: " + msg + " from " +    rinfo.address + ":" + rinfo.port);});server.on("listening", function () {  var address = server.address();  console.log("server listening " +      address.address + ":" + address.port);});server.bind(41234);// server listening 0.0.0.0:41234

socket.bind(options[, callback])

  • options{对象} - 必需. 有以下的属性:
    • port{Number} - 必需.
    • address{字符串} - 可选.
    • exclusive{Boolean} - 可选.
  • callback{函数} - 可选.

options的可选参数portaddress,以及可选参数callback,好像在调用socket.bind(port, [address], [callback])

如果exclusivefalse(默认),集群进程将会使用相同的底层句柄,允许连接处理共享的任务。当exclusivetrue时,句柄不会共享,尝试共享端口也会失败。监听exclusive端口的例子如下:

socket.bind({  address: 'localhost',  port: 8000,  exclusive: true});

socket.close()

关闭底层socket并且停止监听数据。

socket.address()

返回一个包含套接字地址信息的对象。对于UDP socket,这个对象会包含addressfamilyport

socket.setBroadcast(flag)

  • flagBoolean

设置或清除SO_BROADCASTsocket选项。设置这个选项后,UDP包可能会发送给一个本地的接口广播地址。

socket.setTTL(ttl)

  • ttl整数

设置IP_TTLsocket选项。TTL表示生存时间(Time to Live),但是在这个上下文中它指的是报文允许通过的IP跃点数。各个转发报文的路由器或者网关都会递减 TTL。如果TTL被路由器递减为0,则它将不会被转发。改变TTL的值通常用于网络探测器或多播。

setTTL()的参数为1到255的跃点数。多数系统默认值为64。

socket.setMulticastTTL(ttl)

  • ttl整数

设置IP_MULTICAST_TTLsocket选项。TTL表示生存时间(Time to Live),但是在这个上下文中它指的是报文允许通过的IP跃点数。各个转发报文的路由器或者网关都会递减TTL。如果TTL被路由器递减为0,则它将不会被转发。改变TTL的值通常用于网络探测器或多播。

setMulticastTTL()的参数为1到255的跃点数。多数系统默认值为1。

socket.setMulticastLoopback(flag)

  • flag Boolean

设置或清空IP_MULTICAST_LOOPsocket选项。设置完这个选项后,当该选项被设置时,组播报文也会被本地接口收到。

socket.addMembership(multicastAddress[, multicastInterface])

  • multicastAddress字符串
  • multicastInterface字符串,可选

告诉内核加入广播组,选项为IP_ADD_MEMBERSHIPsocket

如果没有指定multicastInterface,操作系统会给所有可用的接口添加关系。

socket.dropMembership(multicastAddress[, multicastInterface])

  • multicastAddress字符串
  • multicastInterface字符串,可选

addMembership相反 - 用IP_DROP_MEMBERSHIP选项告诉内核离开广播组 。如果没有指定multicastInterface,操作系统会移除所有可用的接口关系。

socket.unref()

在socket上调用unref允许程序退出,如果这是在事件系统中唯一的活动socket。如果socket已经unref,再次调用unref将会无效。

socket.ref()

unref相反,如果这是唯一的socket,在一个之前被unref了的socket上调用ref将不会让程序退出(缺省行为)。如果一个socket已经被ref,则再次调用ref将会无效。


稳定性: 3 - 稳定

Node.js的URL模块提供了用于分析和解析URL的实用程序。可以调用require('url')来访问它:

const url = require('url');

解析URL对象有以下内容,依赖于他们是否在URL字符串里存在。任何不在URL字符串里的部分,都不会出现在解析对象里。例子如下:

'http://user:pass@host.com:8080/p/a/t/h?query=string#hash'

  • href:准备解析的完整的URL,包含协议和主机(小写)。

    例子:'http://user:pass@host.com:8080/p/a/t/h?query=string#hash'

  • protocol: 请求协议,小写。

    例子:'http:'

  • slashes: 协议要求的斜杠(冒号后)

    例子:true或false

  • host: 完整的URL小写主机部分,包含端口信息。

    例子:'host.com:8080'

  • auth: url中的验证信息。

    例子:'user:pass'

  • hostname: 域名中的小写主机名

    例子:'host.com'

  • port: 主机的端口号

    例子:'8080'

  • pathname: URL中的路径部分,在主机名后,查询字符前,包含第一个斜杠。

    例子:'/p/a/t/h'

  • search: URL中得查询字符串,包含开头的问号

    例子:'?query=string'

  • path: pathnamesearch连在一起

    例子:'/p/a/t/h?query=string'

  • query: 查询字符串中得参数部分,或者使用querystring.parse()解析后返回的对象。

    例子:'query=string'或者{'query':'string'}

  • hash: URL的“#”后面部分(包括 # 符号)

    例子:'#hash'

URL模块提供了以下方法:

url.parse(urlStr[, parseQueryString][, slashesDenoteHost])

输入URL字符串,返回一个对象。

第二个参数为true时,使用querystring来解析查询字符串。如果为truequery属性将会一直赋值为对象,并且search属性将会一直是字符串(可能为空)。默认为false

第三个参数为true,把//foo/bar当做{ host: 'foo', pathname: '/bar' } ,而不是{ pathname: '//foo/bar' }。默认为false

url.format(urlObj)

输入一个解析过的URL对象,返回格式化过的字符串。

格式化的工作流程:

  • href会被忽略
  • protocol无论是否有末尾的 : (冒号),会同样的处理
    • httphttpsftpgopherfile协议会被添加后缀://
    • mailtoxmppaimsftpfoo等协议添加后缀:
  • slashes如果协议需要://,设置为true。
    • 仅需对之前列出的没有斜杠的协议,比如议mongodb://localhost:8000/
  • auth如果出现将会使用.
  • hostname仅在缺少host时使用
  • port仅在缺少host时使用
  • host用来替换hostnameport
  • pathname无论结尾是否有“/”将会同样处理
  • search将会替 query属性
    • 无论前面是否有“/”将会同样处理
  • query (对象;参见querystring) 如果没有search,将会使用
  • hash无论前面是否有#,都会同样处理

url.resolve(from, to)

给一个基础URL,href URL,如同浏览器一样的解析它们可以带上锚点,例如:

url.resolve('/one/two/three', 'four')         // '/one/two/four'url.resolve('http://example.com/', '/one')    // 'http://example.com/one'url.resolve('http://example.com/one', '/two') // 'http://example.com/two'


稳定性: 4 - 锁定

本节介绍Node.js的'util'模块中的函数的使用,通过require('util')访问该模块,如下所示:

const util = require('util');

util 模块原先设计的初衷是用来支持Node.js的内部API的。这里的很多的函数对你的程序来说都非常有用。如果你觉得这些函数不能满足你的要求,那你可以写自己的工具函数。我们不希望'util'模块里添加对于node内部函数无用的扩展。

util.debuglog(section)

  • section{字符串} 被调试的程序节点部分
  • Returns: {Function} 日志函数

用来创建一个有条件的写到stderr的函数(基于NODE_DEBUG环境变量)。如果section出现在环境变量里,返回函数将会和console.error()类似。否则,返回一个空函数。

例如:

javascriptvar debuglog = util.debuglog('foo');var bar = 123;debuglog('hello from foo [%d]', bar);

如果这个程序以NODE_DEBUG=foo的环境运行,将会输出:

FOO 3245: hello from foo [123]

3245是进程ID。如果没有运行在这个环境变量里,将不会打印任何东西。

可以用逗号切割多个NODE_DEBUG环境变量。例如:NODE_DEBUG=fs,net,tls

util.format(format[, ...])

使用第一个参数返回一个格式化的字符串,类似printf

第一个参数是字符串,它包含0或更多的占位符。每个占位符被替换成想要参数转换的值。支持的占位符包括:

  • %s- 字符串.
  • %d- 数字 (整数和浮点数).
  • %j- JSON。如果参数包含循环引用,将会用字符串替换R
  • %%- 单独一个百分号 ('%')。 不会消耗一个参数。

如果占位符没有包含一个相应的参数,占位符不会被替换。

util.format('%s:%s', 'foo'); // 'foo:%s'

如果参数超过占位符,多余的参数将会用util.inspect()转换成字符串,并拼接在一起,用空格隔开。

util.format('%s:%s', 'foo', 'bar', 'baz'); // 'foo:bar baz'

如果第一个参数不是格式化字符串,那么util.format()会返回所有参数拼接成的字符串(空格分割)。每个参数都会用util.inspect()转换成字符串。

util.format(1, 2, 3); // '1 2 3'

util.log(string)

stdout输出并带有时间戳:

require('util').log('Timestamped message.');

util.inspect(object[, options])

返回一个对象的字符串表现形式,在代码调试的时候非常有用。

通过加入一些可选选项,来改变对象的格式化输出形式:

  • showHidden- 如果为true,将会显示对象的不可枚举属性。默认为false

  • depth- 告诉inspect格式化对象时递归多少次。这在格式化大且复杂对象时非常有用。默认为 2。如果想无穷递归的话,传null

  • colors- 如果为true,输出内容将会格式化为有颜色的代码。默认为false, 颜色可以自定义,参见下文。

  • customInspect- 如果为false,那么定义在被检查对象上的inspect(depth, opts) 方法将不会被调用。 默认为true。

检查util对象上所有属性的例子:

var util = require('util');console.log(util.inspect(util, { showHidden: true, depth: null }));

当被调用的时候,参数值可以提供自己的自定义inspect(depth, opts)方法。该方法会接收当前的递归检查深度,以及传入util.inspect()的其他参数。

自定义util.inspect颜色

util.inspect通过util.inspect.stylesutil.inspect.colors对象,自定义全局的输出颜色,

util.inspect.stylesutil.inspect.colors组成风格颜色的一对映射。

高亮风格和他们的默认值:

  • 数字 (黄色)
  • boolean (黄色)
  • 字符串 (绿色)
  • date (洋红)
  • regexp (红色)
  • null (粗体)
  • undefined (斜体)
  • special (青绿色)
  • name (内部用,不是风格)

预定义的颜色为: white斜体blackbluecyan绿色洋红红色黄色以及粗体斜体下划线反选风格。

对象自定义inspect()函数

对象也能自定义inspect(depth)函数, 当使用util.inspect()检查该对象的时候,将会执行对象自定义的检查方法:

var util = require('util');var obj = { name: 'nate' };obj.inspect = function(depth) {  return '{' + this.name + '}';};util.inspect(obj);  // "{nate}"

你可以返回另外一个对象,返回的字符串会根据返回的对象格式化。这和JSON.stringify()的工作流程类似。您还可以完全返回另一个对象,返回的字符串将根据返回的对象格式化。这与JSON.stringify()的工作方式类似:

var obj = { foo: 'this will not show up in the inspect() output' };obj.inspect = function(depth) {  return { bar: 'baz' };};util.inspect(obj);  // "{ bar: 'baz' }"

util.isArray(object)

Array.isArray的内部别名。

如果参数"object"是数组,返回true ,否则返回false

var util = require('util');util.isArray([])  // trueutil.isArray(new Array)  // trueutil.isArray({})  // false

util.isRegExp(object)

如果参数"object"是RegExp返回true ,否则返回false

var util = require('util');util.isRegExp(/some regexp/)  // trueutil.isRegExp(new RegExp('another regexp'))  // trueutil.isRegExp({})  // false

util.isDate(object)

如果参数"object"是Date返回true,否则返回false

var util = require('util');util.isDate(new Date())  // trueutil.isDate(Date())  // false (without 'new' returns a String)util.isDate({})  // false

util.isError(object)

如果参数"object"是Error返回true,否则返回false

var util = require('util');util.isError(new Error())  // trueutil.isError(new TypeError())  // trueutil.isError({ name: 'Error', message: 'an error occurred' })  // false

util.inherits(constructor, superConstructor)

从一个构造函数constructor继承原型方法到另一个。构造函数的原型将被设置为一个新的从超类(superConstructor)创建的对象。

通过constructor.super_属性可以访问superConstructor

var util = require("util");var events = require("events");function MyStream() {    events.EventEmitter.call(this);}util.inherits(MyStream, events.EventEmitter);MyStream.prototype.write = function(data) {    this.emit("data", data);}var stream = new MyStream();console.log(stream instanceof events.EventEmitter); // trueconsole.log(MyStream.super_ === events.EventEmitter); // truestream.on("data", function(data) {    console.log('Received data: "' + data + '"');})stream.write("It works!"); // Received data: "It works!"

util.deprecate(function, string)

标明该方法不要再使用。

exports.puts = exports.deprecate(function() {  for (var i = 0, len = arguments.length; i < len; ++i) {    process.stdout.write(arguments[i] + '
');  }}, 'util.puts: Use console.log instead')

返回一个修改过的函数,默认情况下仅警告一次。如果设置了--no-deprecation该函数不做任何事。如果设置了--throw-deprecation,如果使用了该API应用将会抛出异常。

util.debug(string)

稳定性: 0 - 抛弃: 使用 console.error() 替换。

console.error的前身。

util.error([...])

稳定性: 0 - 抛弃: 使用 console.error() 替换。

console.error的前身。

util.puts([...])

稳定性: 0 - 抛弃:使用 console.log() 替换。

console.log的前身。

util.print([...])

稳定性: 0 - 抛弃: 使用 console.log() 替换。

console.log的前身。

util.pump(readableStream, writableStream[, callback])

稳定性: 0 - 抛弃: Use readableStream.pipe(writableStream)

stream.pipe的前身。


稳定性: 3 - 稳定

本节介绍了Node.js的虚拟机(VM)模块,该模块提供了用于在V8虚拟机上下文中编译和运行代码的API。

可以通过以下方法访问该模块:

var vm = require('vm');

JavaScript 可以立即编译立即执行,也可以编译,保存,之后再运行。

vm.runInThisContext(code[, options])

vm.runInThisContext()对参数code编译,运行并返回结果。运行的代码没有权限访问本地作用域(local scope),但是可以访问全局对象。

使用vm.runInThisContexteval方法运行同样代码的例子:

var localVar = 'initial value';var vmResult = vm.runInThisContext('localVar = "vm";');console.log('vmResult: ', vmResult);console.log('localVar: ', localVar);var evalResult = eval('localVar = "eval";');console.log('evalResult: ', evalResult);console.log('localVar: ', localVar);// vmResult: 'vm', localVar: 'initial value'// evalResult: 'eval', localVar: 'eval'

vm.runInThisContext没有访问本地作用域,所以没有改变localVareval范围了本地作用域,所以改变了localVar

vm.runInThisContext用起来很像间接调用eval,比如(0,eval)('code')。但是,vm.runInThisContext也包含以下选项:

  • filename: 允许更改显示在站追踪(stack traces)的文件名。
  • displayErrors: 是否在stderr上打印错误,抛出异常的代码行高亮显示。会捕获编译时的语法错误,和执行时抛出的错误。默认为true
  • timeout: 中断前代码执行的毫秒数。如果执行终止,将会抛出错误。

vm.createContext([sandbox])

如果参数sandbox不为空,调用vm.runInContextscript.runInContext时可以调用沙箱的上下文。以此方式运行的脚本,sandbox是全局对象,它保留自己的属性同时拥有标准全局对象(global object)拥有的内置对象和函数。

如果参数sandbox对象为空,返回一个可用的新且空的上下文相关的沙盒对象。

这个函数对于创建一个可运行多脚本的沙盒非常有用。比如,在模拟浏览器的时候可以使用该函数创建一个用于表示window全局对象的沙箱,并将所有<script>标签放入沙箱执行。

vm.isContext(sandbox)

沙箱对象是否已经通过调用vm.createContext上下文化。

vm.runInContext(code, contextifiedSandbox[, options])

vm.runInContext编译代码,运行在contextifiedSandbox并返回结果。运行代码不能访问本地域。contextifiedSandbox对象必须通过 vm.createContext上下文化;code 会通过全局变量使用它。

vm.runInContextvm.runInThisContext参数相同。

在同一个上下文中编译并执行不同的脚本,例子:

var util = require('util');var vm = require('vm');var sandbox = { globalVar: 1 };vm.createContext(sandbox);for (var i = 0; i < 10; ++i) {    vm.runInContext('globalVar *= 2;', sandbox);}console.log(util.inspect(sandbox));// { globalVar: 1024 }

注意,执行不被信任的代码是需要技巧且要非常的小心。vm.runInContext非常有用,不过想要安全些,最好还是在独立的进程里运行不被信任的代码。

vm.runInNewContext(code[, sandbox][, options])

vm.runInNewContext编译代码, 如果提供了sandbox ,则将sandbox上下文化,否则创建一个新的上下文化过的沙盒,将沙盒作为全局变量运行代码并返回结果。

vm.runInNewContextvm.runInThisContext参数相同。

编译并执行代码,增加全局变量值,并设置一个新的。这些全局变量包含在一个新的沙盒里。

var util = require('util');var vm = require('vm'),var sandbox = {  animal: 'cat',  count: 2};vm.runInNewContext('count += 1; name = "kitty"', sandbox);console.log(util.inspect(sandbox));// { animal: 'cat', count: 3, name: 'kitty' }

注意,执行不被信任的代码是需要技巧且要非常的小心。vm.runInNewContext非常有用,不过想要安全些,最好还是在独立的进程里运行不被信任的代码。

vm.runInDebugContext(code)

vm.runInDebugContext在V8的调试上下文中编译并执行。最主要的应用场景是获得V8调试对象访问权限。

var Debug = vm.runInDebugContext('Debug');Debug.scripts().forEach(function(script) { console.log(script.name); });

注意,调试上下文和对象内部绑定到V8的调试实现里,并可能在没有警告时改变(或移除)。

可以通过--expose_debug_as=开关暴露调试对象。

Class: Script

包含预编译脚本的类,并在指定的沙盒里执行。

new vm.Script(code, options)

创建一个新的脚本编译代码,但是不运行。使用被创建的vm.Script来表示编译完的代码。这个代码可以使用以下的方法调用多次。返回的脚本没有绑定到任何全局变量。在运行前绑定,执行后释放。

创建脚本的选项有:

  • filename: 允许更改显示在站追踪(stack traces)的文件名。
  • displayErrors: 是否在stderr上打印错误,抛出异常的代码行高亮显示。只会捕获编译时的语法错误,执行时抛出的错误由脚本的方法的选项来控制。默认为 true

script.runInThisContext([options])

vm.runInThisContext类似,只是作为Script脚本对象的预编译方法。script.runInThisContext执行编译过的脚本并返回结果。被运行的代码没有本地作用域访问权限,但是拥有权限访问全局对象。

以下例子,使用script.runInThisContext编译代码一次,并运行多次:

var vm = require('vm');global.globalVar = 0;var script = new vm.Script('globalVar += 1', { filename: 'myfile.vm' });for (var i = 0; i < 1000; ++i) {  script.runInThisContext();}console.log(globalVar);// 1000

所运行的代码选项:

  • displayErrors: 是否在stderr上打印错误,抛出异常的代码行高亮显示。仅适用于执行时抛出的错误。不能创建一个语法错误的Script实例,因为构造函数会抛出。
  • timeout: 中断前代码执行的毫秒数。如果执行终止,将会抛出错误。

script.runInContext(contextifiedSandbox[, options])

vm.runInContext类似,只是作为预编译的Script对象方法。script.runInContext运行脚本(在contextifiedSandbox中编译)并返回结果。运行的代码没有权限访问本地域。

script.runInContext的选项和script.runInThisContext类似。

例子:编译一段代码,并执行多次,这段代码实现了一个全局变量的自增,并创建一个新的全局变量。这些全局变量保存在沙盒里。

var util = require('util');var vm = require('vm');var sandbox = {  animal: 'cat',  count: 2};var script = new vm.Script('count += 1; name = "kitty"');for (var i = 0; i < 10; ++i) {  script.runInContext(sandbox);}console.log(util.inspect(sandbox));// { animal: 'cat', count: 12, name: 'kitty' }

注意,执行不被信任的代码是需要技巧且要非常的小心。script.runInContext非常有用,不过想要安全些,最好还是在独立的进程里运行不被信任的代码。

script.runInNewContext([sandbox][, options])

vm.runInNewContext类似,只是作为预编译的Script对象方法。若提供sandbox则script.runInNewContext将sandbox上下文化,若未提供,则创建一个新的上下文化的沙箱。

script.runInNewContextscript.runInThisContext的参数类似。

例子:编译代码(设置了一个全局变量)并在不同的上下文里执行多次。这些全局变量会被保存在沙箱中。

var util = require('util');var vm = require('vm');var sandboxes = [{}, {}, {}];var script = new vm.Script('globalVar = "set"');sandboxes.forEach(function (sandbox) {  script.runInNewContext(sandbox);});console.log(util.inspect(sandboxes));// [{ globalVar: 'set' }, { globalVar: 'set' }, { globalVar: 'set' }]

注意,执行不被信任的代码是需要技巧且要非常的小心。script.runInNewContext非常有用,不过想要安全些,最好还是在独立的进程里运行不被信任的代码。


稳定性: 3 - 文档

本节介绍Node.js中ZLIB模块的使用,你可以通过以下方式访问这个模块:

var zlib = require('zlib');

这个模块提供了对Gzip/Gunzip, Deflate/Inflate, 和 DeflateRaw/InflateRaw类的绑定。每个类都有相同的参数和可读/写的流。

例子

压缩/解压缩一个文件,可以通过倒流(piping)一个fs.ReadStream到zlib流里来,再到一个fs.fs.WriteStream:

var gzip = zlib.createGzip();var fs = require('fs');var inp = fs.createReadStream('input.txt');var out = fs.createWriteStream('input.txt.gz');inp.pipe(gzip).pipe(out);

一步压缩/解压缩数据可以通过一个简便方法来实现。

var input = '.................................';zlib.deflate(input, function(err, buffer) {  if (!err) {    console.log(buffer.toString('base64'));  }});var buffer = new Buffer('eJzT0yMAAGTvBe8=', 'base64');zlib.unzip(buffer, function(err, buffer) {  if (!err) {    console.log(buffer.toString());  }});

要在一个HTTP客户端或服务器中使用这个模块,可以在请求时使用accept-encoding,响应时使用content-encoding头。

注意: 这些例子只是简单展示了基本概念。Zlib编码可能消耗非常大,并且结果可能要被缓存。更多使用 zlib 相关的速度/内存/压缩的权衡选择细节参见后面的Memory Usage Tuning

// client request examplevar zlib = require('zlib');var http = require('http');var fs = require('fs');var request = http.get({ host: 'izs.me',                         path: '/',                         port: 80,                         headers: { 'accept-encoding': 'gzip,deflate' } });request.on('response', function(response) {  var output = fs.createWriteStream('izs.me_index.html');  switch (response.headers['content-encoding']) {    // or, just use zlib.createUnzip() to handle both cases    case 'gzip':      response.pipe(zlib.createGunzip()).pipe(output);      break;    case 'deflate':      response.pipe(zlib.createInflate()).pipe(output);      break;    默认:      response.pipe(output);      break;  }});// server example// Running a gzip operation on every request is quite expensive.// It would be much more efficient to cache the compressed buffer.var zlib = require('zlib');var http = require('http');var fs = require('fs');http.createServer(function(request, response) {  var raw = fs.createReadStream('index.html');  var acceptEncoding = request.headers['accept-encoding'];  if (!acceptEncoding) {    acceptEncoding = '';  }  // Note: this is not a conformant accept-encoding parser.  // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3  if (acceptEncoding.match(/deflate/)) {    response.writeHead(200, { 'content-encoding': 'deflate' });    raw.pipe(zlib.createDeflate()).pipe(response);  } else if (acceptEncoding.match(/gzip/)) {    response.writeHead(200, { 'content-encoding': 'gzip' });    raw.pipe(zlib.createGzip()).pipe(response);  } else {    response.writeHead(200, {});    raw.pipe(response);  }}).listen(1337);

zlib.createGzip([options])

根据参数options返回一个新的Gzip对象。

zlib.createGunzip([options])

根据参数options返回一个新的Gunzip对象。

zlib.createDeflate([options])

根据参数options返回一个新的Deflate对象。

zlib.createInflate([options])

根据参数options返回一个新的Inflate对象。

zlib.createDeflateRaw([options])

根据参数options返回一个新的DeflateRaw对象。

zlib.createInflateRaw([options])

根据参数options返回一个新的InflateRaw对象。

zlib.createUnzip([options])

根据参数options返回一个新的Unzip对象。

Class: zlib.Zlib

这个类未被zlib模块导出。之所以写在这,是因为这是压缩/解压缩类的基类。

zlib.flush([kind], callback)

参数kind默认为zlib.Z_FULL_FLUSH

刷入缓冲数据。不要轻易调用这个方法,过早的刷会对压缩算法产生负面影响。

zlib.params(level, strategy, callback)

动态更新压缩基本和压缩策略。仅对deflate算法有效。

zlib.reset()

重置压缩/解压缩为默认值。仅适用于inflate和deflate算法。

Class: zlib.Gzip

使用gzip压缩数据。

Class: zlib.Gunzip

使用gzip解压缩数据。

Class: zlib.Deflate

使用deflate压缩数据。

Class: zlib.Inflate

解压缩deflate流。

Class: zlib.DeflateRaw

使用deflate压缩数据,不需要拼接zlib头。

Class: zlib.InflateRaw

解压缩一个原始deflate流。

Class: zlib.Unzip

通过自动检测头解压缩一个Gzip-或Deflate-compressed流。

简便方法

所有的这些方法第一个参数为字符串或缓存,第二个可选参数可以供zlib类使用,回调函数为callback(error, result)

每个方法都有一个*Sync伴随方法,它接收相同参数,不过没有回调。

zlib.deflate(buf[, options], callback)

zlib.deflateSync(buf[, options])

使用Deflate压缩一个字符串。

zlib.deflateRaw(buf[, options], callback)

zlib.deflateRawSync(buf[, options])

使用DeflateRaw压缩一个字符串。

zlib.gzip(buf[, options], callback)

zlib.gzipSync(buf[, options])

使用Gzip压缩一个字符串。

zlib.gunzip(buf[, options], callback)

zlib.gunzipSync(buf[, options])

使用Gunzip解压缩一个原始的Buffer。

zlib.inflate(buf[, options], callback)

zlib.inflateSync(buf[, options])

使用Inflate解压缩一个原始的Buffer。

zlib.inflateRaw(buf[, options], callback)

zlib.inflateRawSync(buf[, options])

使用InflateRaw解压缩一个原始的Buffer。

zlib.unzip(buf[, options], callback)

zlib.unzipSync(buf[, options])

使用Unzip解压缩一个原始的Buffer。

Options

每个类都有一个选项对象。所有选项都是可选的。

注意:某些选项仅在压缩时有用,解压缩时会被忽略。

  • flush (默认:zlib.Z_NO_FLUSH)
  • chunkSize (默认:16*1024)
  • windowBits
  • level (仅压缩有效)
  • memLevel (仅压缩有效)
  • strategy (仅压缩有效)
  • dictionary (仅 deflate/inflate 有效, 默认为空字典)

参见deflateInit2inflateInit2的描述,它们位于http://zlib.net/manual.html#Advanced

使用内存调优

来自zlib/zconf.h,修改为node's的用法:

deflate的内存需求(单位:字节):

(1 << (windowBits+2)) +  (1 << (memLevel+9))

windowBits=15的128K加memLevel = 8的128K (缺省值),加其他对象的若干KB。

例如,如果你想减少默认的内存需求(从256K减为128k),设置选项:

{ windowBits: 14, memLevel: 7 }

当然这通常会降低压缩等级。

inflate的内存需求(单位:字节):

1 << windowBits

windowBits=15(默认值)32K 加其他对象的若干KB。

这是除了内部输出缓冲外chunkSize的大小,默认为16K。

影响zlib的压缩速度最大因素为level压缩级别。level越大,压缩率越高,速度越慢,level越小,压缩率越小,速度会更快。

通常来说,使用更多的内存选项,意味着node必须减少对zlib掉哟过,因为可以在一个write操作里可以处理更多的数据。所以,这是另一个影响速度和内存使用率的因素,

常量

所有常量定义在zlib.h ,也定义在require('zlib')

通常的操作,基本用不到这些常量。写到文档里是想你不会对他们的存在感到惊讶。这个章节基本都来自zlibdocumentation。更多细节参见http://zlib.net/manual.html#Constants

允许flush的值:

  • zlib.Z_NO_FLUSH
  • zlib.Z_PARTIAL_FLUSH
  • zlib.Z_SYNC_FLUSH
  • zlib.Z_FULL_FLUSH
  • zlib.Z_FINISH
  • zlib.Z_BLOCK
  • zlib.Z_TREES

压缩/解压缩函数的返回值。负数代表错误,正数代表特殊但正常的事件:

  • zlib.Z_OK
  • zlib.Z_STREAM_END
  • zlib.Z_NEED_DICT
  • zlib.Z_ERRNO
  • zlib.Z_STREAM_ERROR
  • zlib.Z_DATA_ERROR
  • zlib.Z_MEM_ERROR
  • zlib.Z_BUF_ERROR
  • zlib.Z_VERSION_ERROR

压缩级别:

  • zlib.Z_NO_COMPRESSION
  • zlib.Z_BEST_SPEED
  • zlib.Z_BEST_COMPRESSION
  • zlib.Z_DEFAULT_COMPRESSION

压缩策略:

  • zlib.Z_FILTERED
  • zlib.Z_HUFFMAN_ONLY
  • zlib.Z_RLE
  • zlib.Z_FIXED
  • zlib.Z_DEFAULT_STRATEGY

data_type字段的可能值:

  • zlib.Z_BINARY
  • zlib.Z_TEXT
  • zlib.Z_ASCII
  • zlib.Z_UNKNOWN

deflate的压缩方法:

  • zlib.Z_DEFLATED

初始化zalloc, zfree, opaque:

  • zlib.Z_NULL
//www.51coolma.cn/nodejs/node-js-tutorial.html

Node.js v8.3.0已发布,在该版本中,已将V8引擎升级到6.0版本,性能有了大幅度的改进。有关性能差异的更多详细信息,点击查看详情

除此之外,还带来了循环遍历对象,删除对象键,函数绑定和对象创建等实用的功能。下面W3C小编就给大家带来此次更新的一些主要内容。

  • DNS
  • 现在支持独立的 DNS 解析程序实例, 并支持取消相应的请求。#14518
  • N-API
  • 错误处理的多个N-API函数已更改为支持分配错误代码。#13988
  • REPL
  • 对require()的自动完成支持已经改进。#14409
  • Utilities
  • WHATWG编码标准 (TextDecoder 和 TextEncoder) 作为一个实验性的特点被实现。#13644
  • 增加了新collaborators
  • XadillaX – Khaidi Chugabrielschulhof – Gabriel Schulhof

相关下载地址:

Node.js是一个基于 Chrome V8 引擎的JavaScript运行时。Node.js使用高效、轻量级的事件驱动、非阻塞I/O模型。

nodejs

Node.js是运行在服务端的JavaScript。

Node.js是一个基于Chrome JavaScript运行时建立的一个平台。

Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。



Node.js教程适用人群

对于不会运用Python、PHP以及Java等动态编程语言的前端程序员来说,选择Node.js作为一个创建自己的服务的工具是非常明智的。

Node.js是运行在服务端的JavaScript,因此,熟悉Javascript的使用将有助于学习Node.js。

同时,学习该Node.js教程也可以帮助后端程序员部署一些高性能的服务。


学习本教程前你需要了解

在继续本教程之前,你应该了解一些基本的计算机编程术语。如果你学习过Javascript,PHP,Java等编程语言,将有助于你更快的了解Node.js编程。


第一个Node.js程序:Hello World!

脚本模式

以下是我们的第一个Node.js程序:

console.log("Hello World");

保存该文件,文件名为helloworld.js,并通过node命令来执行:

node helloworld.js

程序执行后,正常的话,就会在终端输出Hello World。

交互模式

打开终端,键入node进入命令交互模式,可以输入一条代码语句后立即执行并显示结果,例如:

$ node> console.log('Hello World!');Hello World!

相关教程

JavaScript教程

PHP教程

Java教程

本章节我们将向大家介绍在window和Linux上安装Node.js的方法。

本安装教程以 Node.js v4.4.3 LTS(长期支持版本)版本为例。

Node.js 安装包及源码下载地址为:https://nodejs.org/en/download/


你可以根据不同平台系统选择你需要的 Node.js 安装包。

Node.js 历史版本下载地址:https://nodejs.org/dist/

注意:Linux 上安装 Node.js 需要安装 Python 2.6 或 2.7 ,不建议安装 Python 3.0 以上版本。


Windowv 上安装Node.js

1、Windows 安装包(.msi)

32 位安装包下载地址 : http://nodejs.org/dist/v0.10.26/node-v0.10.26-x86.msi

64 位安装包下载地址 : http://nodejs.org/dist/v0.10.26/x64/node-v0.10.26-x64.msi


本文实例以 v0.10.26 版本为例,其他版本类似。

安装步骤:

步骤 1 : 双击下载后的安装包 node-v0.10.26-x86.msi,如下所示:

install-node-msi-version-on-windows-step1

步骤 2 : 点击以上的Run(运行),将出现如下界面:

install-node-msi-version-on-windows-step2

步骤 3 : 勾选接受协议选项,点击 next(下一步) 按钮 :

install-node-msi-version-on-windows-step3

步骤 4 : Node.js默认安装目录为 "C:Program Files odejs" , 你可以修改目录,并点击 next(下一步):

install-node-msi-version-on-windows-step4

步骤 5 : 点击树形图标来选择你需要的安装模式 , 然后点击 next 进入下一步

install-node-msi-version-on-windows-step5

步骤 6 :点击 Install(安装) 开始安装Node.js。你也可以点击 Back(返回)来修改先前的配置。 然后并点击 next进入下一步:

install-node-msi-version-on-windows-step6

安装过程:

install-node-msi-version-on-windows-step7

点击 Finish(完成)按钮退出安装向导。

install-node-msi-version-on-windows-step8

检测PATH环境变量是否配置了Node.js,点击开始=》运行=》输入"cmd" => 输入命令"path",输出如下结果:

PATH=C:oraclexeapporacleproduct10.2.0serverin;C:Windowssystem32;C:Windows;C:WindowsSystem32Wbem;C:WindowsSystem32WindowsPowerShellv1.0;c:python32python;C:MinGWin;C:Program FilesGTK2-Runtimelib;C:Program FilesMySQLMySQL Server 5.5in;C:Program Files
odejs;C:Users
gAppDataRoaming
pm

我们可以看到环境变量中已经包含了 C:Program Files odejs

检查Node.js版本

node-version-test

2、Windows 二进制文件 (.exe)安装

32 位安装包下载地址 : http://nodejs.org/dist/v0.10.26/node.exe

64 位安装包下载地址 : http://nodejs.org/dist/v0.10.26/x64/node.exe

安装步骤

步骤 1 : 双击下载的安装包 Node.exe ,将出现如下界面 :

install-node-exe-on-windows-step1

点击 Run(运行)按钮将出现命令行窗口:

install-node-exe-on-windows-step21

版本测试:

输入截图中命令,进入 node.exe 所在的目录,如下所示:

node-version

如果你获得以上输出结果,说明你已经成功安装了Node.js。


Linux上安装 Node.js

直接使用已编译好的包

Node 官网已经把 linux 下载版本更改为已编译好的版本了,我们可以直接下载解压后使用:

# wget https://nodejs.org/dist/v10.9.0/node-v10.9.0-linux-x64.tar.xz    // 下载# tar xf  node-v10.9.0-linux-x64.tar.xz       // 解压# cd node-v10.9.0-linux-x64/                  // 进入解压目录# ./bin/node -v                               // 执行node命令 查看版本v10.9.0

解压文件的 bin 目录底下包含了 node、npm 等命令,我们可以使用 ln 命令来设置软连接:

ln -s /usr/software/nodejs/bin/npm   /usr/local/bin/ ln -s /usr/software/nodejs/bin/node   /usr/local/bin/

Ubuntu 源码安装 Node.js

以下部分我们将介绍在 Ubuntu Linux 下使用源码安装 Node.js 。 其他的 Linux 系统,如 Centos 等类似如下安装步骤。

在 Github 上获取 Node.js 源码:

$ sudo git clone https://github.com/nodejs/node.gitCloning into 'node'...

修改目录权限:

$ sudo chmod -R 755 node

使用 ./configure 创建编译文件,并按照:

$ cd node$ sudo ./configure$ sudo make$ sudo make install

查看 node 版本:

$ node --versionv0.10.25

Ubuntu apt-get命令安装

命令格式如下:

sudo apt-get install nodejssudo apt-get install npm

CentOS 下源码安装 Node.js

1、下载源码,你需要在https://nodejs.org/en/download/下载最新的Nodejs版本,本文以v0.10.24为例:

cd /usr/local/src/wget http://nodejs.org/dist/v0.10.24/node-v0.10.24.tar.gz

2、解压源码

tar zxvf node-v0.10.24.tar.gz

3、 编译安装

cd node-v0.10.24./configure --prefix=/usr/local/node/0.10.24makemake install

4、 配置NODE_HOME,进入profile编辑环境变量

vim /etc/profile

设置 nodejs 环境变量,在 export PATH USER LOGNAME MAIL HOSTNAME HISTSIZE HISTCONTROL 一行的上面添加如下内容:

#set for nodejsexport NODE_HOME=/usr/local/node/0.10.24export PATH=$NODE_HOME/bin:$PATH

:wq保存并退出,编译/etc/profile 使配置生效

source /etc/profile

验证是否安装配置成功

node -v

输出 v0.10.24 表示配置成功

npm模块安装路径

/usr/local/node/0.10.24/lib/node_modules/

注:Nodejs 官网提供了编译好的 Linux 二进制包,你也可以下载下来直接应用。

Mac OS 上安装

你可以通过以下两种方式在 Mac OS 上来安装 node:

  • 1、在官方下载网站下载 pkg 安装包,直接点击安装即可。
  • 2、使用 brew 命令来安装:brew install node


Node.js 非常强大,只需动手写几行代码就可以构建出整个HTTP服务器。事实上,我们的Web应用以及对应的Web服务器基本上是一样的。

在我们创建Node.js第一个"Hello, World!"应用前,让我们先了解下Node.js应用是由哪几部分组成的:

  1. 引入模块(required):我们可以使用require指令来载入Node.js模块。

  2. 创建服务器:服务器可以监听客户端的请求,类似于Apache 、Nginx等HTTP服务器。

  3. 接收请求与响应请求:服务器很容易创建,客户端可以使用浏览器或终端发送HTTP请求,服务器接收请求后返回响应数据。


创建 Node.js 应用

步骤一、引入required模块

我们使用require指令来载入http模块,并将实例化的HTTP赋值给变量http,实例如下:

var http = require("http");

步骤二、创建服务器

接下来我们使用http.createServer()方法创建服务器,并使用listen方法绑定8888端口。 函数通过request, response参数来接收和响应数据。

实例如下,在你本地计算机中创建一个文件项目,并在这个文件项目中的根目录下创建一个叫server.js的文件,并写入以下代码:

如下项目截图所示:

2

server.js的文件代码如下:

var http = require('http');http.createServer(function (request, response) {	// 发送 HTTP 头部 	// HTTP 状态值: 200 : OK	// 内容类型: text/plain	response.writeHead(200, {'Content-Type': 'text/plain'});	// 发送响应数据 "Hello World"	response.end('Hello World
');}).listen(8888);// 终端打印如下信息console.log('Server running at http://127.0.0.1:8888/');

以上代码我们完成了一个可以工作的HTTP服务器。

在文件中打开Powershell窗口如下所示:

2

之后在执行下面的命令既可:

node server.jsServer running at http://127.0.0.1:8888/

3

接下来,打开浏览器访问http://127.0.0.1:8888/,你会看到一个写着"Hello World"的网页。

1

分析Node.js的HTTP服务器:

  • 第一行请求(require)Node.js自带的 http 模块,并且把它赋值给http变量。
  • 接下来我们调用http模块提供的函数:createServer 。这个函数会返回 一个对象,这个对象有一个叫做listen的方法,这个方法有一个数值参数,指定这个HTTP服务器监听的端口号。

Gif 实例演示

接下来我们通过Gif图为大家演示实例操作:

1


本文介绍了 Node.js 中 NPM 的使用,我们先来了解什么是 NPM。

NPM是随同NodeJS一起安装的包管理工具,能解决NodeJS代码部署上的很多问题,常见的使用场景有以下几种:

  • 允许用户从NPM服务器下载别人编写的第三方包到本地使用。
  • 允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用。
  • 允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用。

由于新版的nodejs已经集成了npm,所以之前npm也一并安装好了。同样可以通过输入npm -v来测试是否成功安装。命令如下,出现版本提示表示安装成功:

$ npm -v2.3.0

如果你安装的是旧版本的 npm,可以很容易得通过 npm 命令来升级,命令如下:

$ sudo npm install npm -g/usr/local/bin/npm -> /usr/local/lib/node_modules/npm/bin/npm-cli.jsnpm@2.14.2 /usr/local/lib/node_modules/npm

如果是 Window 系统使用以下命令即可:

npm install npm -g
使用淘宝镜像的命令:
npm install -g cnpm --registry=https://registry.npm.taobao.org

使用 npm 命令安装模块

npm 安装 Node.js 模块语法格式如下:

$ npm install <Module Name>

以下实例,我们使用 npm 命令安装常用的 Node.js web框架模块 express:

$ npm install express

安装好之后,express 包就放在了工程目录下的 node_modules 目录中,因此在代码中只需要通过 require('express') 的方式就好,无需指定第三方包路径。

var express = require('express');

全局安装与本地安装

npm 的包安装分为本地安装(local)、全局安装(global)两种,从敲的命令行来看,差别只是有没有-g而已,比如

npm install express          # 本地安装npm install express -g   # 全局安装

如果出现以下错误:

npm err! Error: connect ECONNREFUSED 127.0.0.1:8087 

解决办法为:

$ npm config set proxy null

本地安装

  • 1. 将安装包放在 ./node_modules 下(运行 npm 命令时所在的目录),如果没有 node_modules 目录,会在当前执行 npm 命令的目录下生成 node_modules 目录。
  • 2. 可以通过 require() 来引入本地安装的包。

全局安装

  • 1. 将安装包放在 /usr/local 下或者你 node 的安装目录。
  • 2. 可以直接在命令行里使用。

如果你希望具备两者功能,则需要在两个地方安装它或使用 npm link。

接下来我们使用全局方式安装 express

$ npm install express -g

安装过程输出如下内容,第一行输出了模块的版本号及安装位置。

express@4.13.3 node_modules/express├── escape-html@1.0.2├── range-parser@1.0.2├── merge-descriptors@1.0.0├── array-flatten@1.1.1├── cookie@0.1.3├── utils-merge@1.0.0├── parseurl@1.3.0├── cookie-signature@1.0.6├── methods@1.1.1├── fresh@0.3.0├── vary@1.0.1├── path-to-regexp@0.1.7├── content-type@1.0.1├── etag@1.7.0├── serve-static@1.10.0├── content-disposition@0.5.0├── depd@1.0.1├── qs@4.0.0├── finalhandler@0.4.0 (unpipe@1.0.0)├── on-finished@2.3.0 (ee-first@1.1.1)├── proxy-addr@1.0.8 (forwarded@0.1.0, ipaddr.js@1.0.1)├── debug@2.2.0 (ms@0.7.1)├── type-is@1.6.8 (media-typer@0.3.0, mime-types@2.1.6)├── accepts@1.2.12 (negotiator@0.5.3, mime-types@2.1.6)└── send@0.13.0 (destroy@1.0.3, statuses@1.2.1, ms@0.7.1, mime@1.3.4, http-errors@1.3.1)

查看安装信息

你可以使用以下命令来查看所有全局安装的模块:

$ npm list -g├─┬ cnpm@4.3.2│ ├── auto-correct@1.0.0│ ├── bagpipe@0.3.5│ ├── colors@1.1.2│ ├─┬ commander@2.9.0│ │ └── graceful-readlink@1.0.1│ ├─┬ cross-spawn@0.2.9│ │ └── lru-cache@2.7.3……

如果要查看某个模块的版本号,可以使用命令如下:

$ npm list gruntprojectName@projectVersion /path/to/project/folder└── grunt@0.4.1

使用 package.json

package.json 位于模块的目录下,用于定义包的属性。接下来让我们来看下 express 包的 package.json 文件,位于 node_modules/express/package.json 内容:

{  "name": "express",  "description": "Fast, unopinionated, minimalist web framework",  "version": "4.13.3",  "author": {    "name": "TJ Holowaychuk",    "email": "tj@vision-media.ca"  },  "contributors": [    {      "name": "Aaron Heckmann",      "email": "aaron.heckmann+github@gmail.com"    },    {      "name": "Ciaran Jessup",      "email": "ciaranj@gmail.com"    },    {      "name": "Douglas Christopher Wilson",      "email": "doug@somethingdoug.com"    },    {      "name": "Guillermo Rauch",      "email": "rauchg@gmail.com"    },    {      "name": "Jonathan Ong",      "email": "me@jongleberry.com"    },    {      "name": "Roman Shtylman",      "email": "shtylman+expressjs@gmail.com"    },    {      "name": "Young Jae Sim",      "email": "hanul@hanul.me"    }  ],  "license": "MIT",  "repository": {    "type": "git",    "url": "git+https://github.com/strongloop/express.git"  },  "homepage": "http://expressjs.com/",  "keywords": [    "express",    "framework",    "sinatra",    "web",    "rest",    "restful",    "router",    "app",    "api"  ],  "dependencies": {    "accepts": "~1.2.12",    "array-flatten": "1.1.1",    "content-disposition": "0.5.0",    "content-type": "~1.0.1",    "cookie": "0.1.3",    "cookie-signature": "1.0.6",    "debug": "~2.2.0",    "depd": "~1.0.1",    "escape-html": "1.0.2",    "etag": "~1.7.0",    "finalhandler": "0.4.0",    "fresh": "0.3.0",    "merge-descriptors": "1.0.0",    "methods": "~1.1.1",    "on-finished": "~2.3.0",    "parseurl": "~1.3.0",    "path-to-regexp": "0.1.7",    "proxy-addr": "~1.0.8",    "qs": "4.0.0",    "range-parser": "~1.0.2",    "send": "0.13.0",    "serve-static": "~1.10.0",    "type-is": "~1.6.6",    "utils-merge": "1.0.0",    "vary": "~1.0.1"  },  "devDependencies": {    "after": "0.8.1",    "ejs": "2.3.3",    "istanbul": "0.3.17",    "marked": "0.3.5",    "mocha": "2.2.5",    "should": "7.0.2",    "supertest": "1.0.1",    "body-parser": "~1.13.3",    "connect-redis": "~2.4.1",    "cookie-parser": "~1.3.5",    "cookie-session": "~1.2.0",    "express-session": "~1.11.3",    "jade": "~1.11.0",    "method-override": "~2.3.5",    "morgan": "~1.6.1",    "multiparty": "~4.1.2",    "vhost": "~3.0.1"  },  "engines": {    "node": ">= 0.10.0"  },  "files": [    "LICENSE",    "History.md",    "Readme.md",    "index.js",    "lib/"  ],  "scripts": {    "test": "mocha --require test/support/env --reporter spec --bail --check-leaks test/ test/acceptance/",    "test-ci": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --require test/support/env --reporter spec --check-leaks test/ test/acceptance/",    "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require test/support/env --reporter dot --check-leaks test/ test/acceptance/",    "test-tap": "mocha --require test/support/env --reporter tap --check-leaks test/ test/acceptance/"  },  "gitHead": "ef7ad681b245fba023843ce94f6bcb8e275bbb8e",  "bugs": {    "url": "https://github.com/strongloop/express/issues"  },  "_id": "express@4.13.3",  "_shasum": "ddb2f1fb4502bf33598d2b032b037960ca6c80a3",  "_from": "express@*",  "_npmVersion": "1.4.28",  "_npmUser": {    "name": "dougwilson",    "email": "doug@somethingdoug.com"  },  "maintainers": [    {      "name": "tjholowaychuk",      "email": "tj@vision-media.ca"    },    {      "name": "jongleberry",      "email": "jonathanrichardong@gmail.com"    },    {      "name": "dougwilson",      "email": "doug@somethingdoug.com"    },    {      "name": "rfeng",      "email": "enjoyjava@gmail.com"    },    {      "name": "aredridel",      "email": "aredridel@dinhe.net"    },    {      "name": "strongloop",      "email": "callback@strongloop.com"    },    {      "name": "defunctzombie",      "email": "shtylman@gmail.com"    }  ],  "dist": {    "shasum": "ddb2f1fb4502bf33598d2b032b037960ca6c80a3",    "tarball": "http://registry.npmjs.org/express/-/express-4.13.3.tgz"  },  "directories": {},  "_resolved": "https://registry.npmjs.org/express/-/express-4.13.3.tgz",  "readme": "ERROR: No README data found!"}

Package.json 属性说明

  • name - 包名。
  • version - 包的版本号。
  • description - 包的描述。
  • homepage - 包的官网 url 。
  • author - 包的作者姓名。
  • contributors - 包的其他贡献者姓名。
  • dependencies - 依赖包列表。如果依赖包没有安装,npm 会自动将依赖包安装在 node_module 目录下。
  • repository - 包代码存放的地方的类型,可以是 git 或 svn,git 可在 Github 上。
  • main - main 字段指定了程序的主入口文件,require('moduleName') 就会加载这个文件。这个字段的默认值是模块根目录下面的 index.js。
  • keywords - 关键字

卸载模块

我们可以使用以下命令来卸载 Node.js 模块。

$ npm uninstall express

卸载后,你可以到 /node_modules/ 目录下查看包是否还存在,或者使用以下命令查看:

$ npm ls

更新模块

我们可以使用以下命令更新模块:

$ npm update express

搜索模块

使用以下来搜索模块:

$ npm search express

创建模块

创建模块,package.json 文件是必不可少的。我们可以使用 NPM 生成 package.json 文件,生成的文件包含了基本的结果。

$ npm initThis utility will walk you through creating a package.json file.It only covers the most common items, and tries to guess sensible defaults.See `npm help json` for definitive documentation on these fieldsand exactly what they do.Use `npm install <pkg> --save` afterwards to install a package andsave it as a dependency in the package.json file.Press ^C at any time to quit.name: (node_modules) 51coolma                   # 模块名version: (1.0.0) description: Node.js 测试模块(www.51coolma.cn)  # 描述
entry point: (index.js) test command: make testgit repository: # Github 地址keywords: author: license: (ISC) About to write to ……/node_modules/package.json: # 生成地址{ "name": "51coolma", "version": "1.0.0", "description": "Node.js 测试模块(www.51coolma.cn)", ……}Is this ok? (yes) yes

以上的信息,你需要根据你自己的情况输入。在最后输入 "yes" 后会生成 package.json 文件。

接下来我们可以使用以下命令在 npm 资源库中注册用户(使用邮箱注册):

$ npm adduserUsername: mcmohdPassword:Email: (this IS public) mcmohd@gmail.com

接下来我们就用以下命令来发布模块:

$ npm publish

如果你以上的步骤都操作正确,你就可以跟其他模块一样使用 npm 来安装。

版本号

使用NPM下载和发布代码时都会接触到版本号。NPM使用语义版本号来管理代码,这里简单介绍一下。

语义版本号分为X.Y.Z三位,分别代表主版本号、次版本号和补丁版本号。当代码变更时,版本号按以下原则更新。

  • 如果只是修复bug,需要更新Z位。
  • 如果是新增了功能,但是向下兼容,需要更新Y位。
  • 如果有大变动,向下不兼容,需要更新X位。

版本号有了这个保证后,在申明第三方包依赖时,除了可依赖于一个固定版本号外,还可依赖于某个范围的版本号。例如"argv": "0.0.x"表示依赖于0.0.x系列的最新版argv。

NPM支持的所有版本号范围指定方式可以查看官方文档

NPM 常用命令

除了本章介绍的部分外,NPM还提供了很多功能,package.json里也有很多其它有用的字段。

除了可以在npmjs.org/doc/查看官方文档外,这里再介绍一些NPM常用命令。

NPM提供了很多命令,例如install和publish,使用npm help可查看所有命令。

  • NPM提供了很多命令,例如install和publish,使用npm help可查看所有命令。
  • 使用npm help <command>可查看某条命令的详细帮助,例如npm help install。
  • 在package.json所在目录下使用npm install . -g可先在本地安装当前命令行程序,可用于发布前的本地测试。
  • 使用npm update <package>可以把当前目录下node_modules子目录里边的对应模块更新至最新版本。
  • 使用npm update <package> -g可以把全局安装的对应命令行程序更新至最新版。
  • 使用npm cache clear可以清空NPM本地缓存,用于对付使用相同版本号发布新版本代码的人。
  • 使用npm unpublish <package>@<version>可以撤销发布自己发布过的某个版本代码。

使用淘宝 NPM 镜像

大家都知道国内直接使用 npm 的官方镜像是非常慢的,这里推荐使用淘宝 NPM 镜像。

淘宝 NPM 镜像是一个完整 npmjs.org 镜像,你可以用此代替官方版本(只读),同步频率目前为 10分钟 一次以保证尽量与官方服务同步。

你可以使用淘宝定制的 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm:

$ npm install -g cnpm --registry=https://registry.npm.taobao.org

这样就可以使用 cnpm 命令来安装模块了:

$ cnpm install [name]

更多信息可以查阅:http://npm.taobao.org/


Node.js REPL(Read Eval Print Loop:交互式解释器) 表示一个电脑的环境,类似 Window 系统的终端或 Unix/Linux shell,我们可以在终端中输入命令,并接收系统的响应。

REPL 的交互式的编程环境可以实时的验证你所编写的代码,非常适合于验证 Node.js 和 JavaScript 的相关 API。

Node 自带了交互式解释器,可以执行以下任务:

  • 读取 - 读取用户输入,解析输入了Javascript 数据结构并存储在内存中。

  • 执行 - 执行输入的数据结构

  • 打印 - 输出结果

  • 循环 - 循环操作以上步骤直到用户两次按下 ctrl-c 按钮退出。

Node 的交互式解释器可以很好的调试 Javascript 代码。

开始学习 REPL

我们可以输入以下命令来启动 Node 的终端:

$ node> 

这时我们就可以在 > 后输入简单的表达式,并按下回车键来计算结果。

简单的表达式运算

接下来让我们在 Node.js REPL 的命令行窗口中执行简单的数学运算:

$ node> 1 +45> 5 / 22.5> 3 * 618> 4 - 13> 1 + ( 2 * 3 ) - 43>

使用变量

你可以将数据存储在变量中,并在你需要的使用它。

变量声明需要使用 var 关键字,如果没有使用 var 关键字变量会直接打印出来。

使用 var 关键字的变量可以使用 console.log() 来输出变量。

$ node> x = 1010> var y = 10undefined> x + y20> console.log("Hello World")Hello Worldundefined> console.log("www.51coolma.cn")www.51coolma.cnundefined

多行表达式

Node REPL 支持输入多行表达式,这就有点类似 JavaScript。接下来让我们来执行一个 do-while 循环:

$ node> var x = 0undefined> do {... x++;... console.log("x: " + x);... } while ( x < 5 );x: 1x: 2x: 3x: 4x: 5undefined>

... 三个点的符号是系统自动生成的,你回车换行后即可。Node 会自动检测是否为连续的表达式。

下划线(_)变量

你可以使用下划线(_)获取表达式的运算结果:

$ node> var x = 10undefined> var y = 20undefined> x + y30> var sum = _undefined> console.log(sum)30undefined>

REPL 命令

  • ctrl + c - 退出当前终端。

  • ctrl + c 按下两次 - 退出 Node REPL。

  • ctrl + d - 退出 Node REPL.

  • 向上/向下 键 - 查看输入的历史命令

  • tab 键 - 列出当前命令

  • .help - 列出使用命令

  • .break - 退出多行表达式

  • .clear - 退出多行表达式

  • .save filename - 保存当前的 Node REPL 会话到指定文件

  • .load filename - 载入当前 Node REPL 会话的文件内容。


停止 REPL

前面我们已经提到按下两次 ctrl + c 建就能退出 REPL:

$ node>(^C again to quit)>

Gif 实例演示

接下来我们通过 Gif 图为大家演示实例操作:

2

相关阅读

JavaScript console对象


Node.js 异步编程的直接体现就是回调。

异步编程依托于回调来实现,但不能说使用了回调后程序就异步化了。

回调函数在完成任务后就会被调用,Node 使用了大量的回调函数,Node 所有 API 都支持回调函数。

例如,我们可以一边读取文件,一边执行其他命令,在文件读取完成后,我们将文件内容作为回调函数的参数返回。这样在执行代码时就没有阻塞或等待文件 I/O 操作。这就大大提高了 Node.js 的性能,可以处理大量的并发请求。

回调函数一般作为函数的最后一个参数出现:

function foo1(name, age, callback) { }function foo2(value, callback1, callback2) { }

阻塞代码实例

创建一个文件 input.txt ,内容如下:

W3Cschool教程官网地址:www.51coolma.cn

创建 main.js 文件, 代码如下:

var fs = require("fs");var data = fs.readFileSync('input.txt');console.log(data.toString());console.log("程序执行结束!");

以上代码执行结果如下:

$ node main.jsW3Cschool教程官网地址:www.51coolma.cn程序执行结束!

非阻塞代码实例

创建一个文件 input.txt ,内容如下:

W3Cschool教程官网地址:www.51coolma.cn

创建 main.js 文件, 代码如下:

var fs = require("fs");fs.readFile('input.txt', function (err, data) {    if (err) return console.error(err);    console.log(data.toString());});console.log("程序执行结束!");

以上代码执行结果如下:

$ node main.js程序执行结束!W3Cschool教程官网地址:www.51coolma.cn

以上两个实例我们了解了阻塞与非阻塞调用的不同。第一个实例在文件读取完后才执行完程序。 第二个实例我们呢不需要等待文件读取完,这样就可以在读取文件时同时执行接下来的代码,大大提高了程序的性能。

因此,阻塞按是按顺序执行的,而非阻塞是不需要按顺序的,所以如果需要处理回调函数的参数,我们就需要写在回调函数内。


Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高。

Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发。

Node.js 基本上所有的事件机制都是用设计模式中观察者模式实现。

Node.js 单线程类似进入一个while(true)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数.


事件驱动程序

Node.js 使用事件驱动模型,当web server接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求。

当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户。

这个模型非常高效可扩展性非常强,因为webserver一直接受请求而不等待任何读写操作。(这也被称之为非阻塞式IO或者事件驱动IO)

在事件驱动模型中,会生成一个主循环来监听事件,当检测到事件时触发回调函数。

整个事件驱动的流程就是这么实现的,非常简洁。有点类似于观察者模式,事件相当于一个主题(Subject),而所有注册到这个事件上的处理函数相当于观察者(Observer)。

Node.js 有多个内置的事件,我们可以通过引入 events 模块,并通过实例化 EventEmitter 类来绑定和监听事件,如下实例:

// 引入 events 模块var events = require('events');// 创建 eventEmitter 对象var eventEmitter = new events.EventEmitter();

以下程序绑定事件处理程序:

// 绑定事件及事件的处理程序eventEmitter.on('eventName', eventHandler);

我们可以通过程序触发事件:

// 触发事件eventEmitter.emit('eventName');

实例

创建 main.js 文件,代码如下所示:

// 引入 events 模块var events = require('events');// 创建 eventEmitter 对象var eventEmitter = new events.EventEmitter();// 创建事件处理程序var connectHandler = function connected() {   console.log('连接成功。');     // 触发 data_received 事件    eventEmitter.emit('data_received');}// 绑定 connection 事件处理程序eventEmitter.on('connection', connectHandler); // 使用匿名函数绑定 data_received 事件eventEmitter.on('data_received', function(){   console.log('数据接收成功。');});// 触发 connection 事件 eventEmitter.emit('connection');console.log("程序执行完毕。");

接下来让我们执行以上代码:

$ node main.js连接成功。数据接收成功。程序执行完毕。

Node 应用程序是如何工作的?

在 Node 应用程序中,执行异步操作的函数将回调函数作为最后一个参数, 回调函数接收错误对象作为第一个参数。

接下来让我们来重新看下前面的实例,创建一个 input.txt ,文件内容如下:

W3Cschool教程官网地址:www.51coolma.cn

创建 main.js 文件,代码如下:

var fs = require("fs");fs.readFile('input.txt', function (err, data) {   if (err){      console.log(err.stack);      return;   }   console.log(data.toString());});console.log("程序执行完毕");

以上程序中 fs.readFile() 是异步函数用于读取文件。 如果在读取文件过程中发生错误,错误 err 对象就会输出错误信息。

如果没发生错误,readFile 跳过 err 对象的输出,文件内容就通过回调函数输出。

执行以上代码,执行结果如下:

程序执行完毕W3Cschool教程官网地址:www.51coolma.cn

接下来我们删除 input.txt 文件,执行结果如下所示:

程序执行完毕Error: ENOENT, open 'input.txt'

因为文件 input.txt 不存在,所以输出了错误信息。


Node.js 所有的异步 I/O 操作在完成时都会发送一个事件到事件队列。

Node.js 里面的许多对象都会分发事件:一个net.Server对象会在每次有新连接时分发一个事件, 一个fs.readStream对象会在文件被打开的时候发出一个事件。 所有这些产生事件的对象都是 events.EventEmitter 的实例。 


EventEmitter 类

events 模块只提供了一个对象: events.EventEmitter。EventEmitter 的核心就是事件触发与事件监听器功能的封装。

你可以通过require("events");来访问该模块。

// 引入 events 模块var events = require('events');// 创建 eventEmitter 对象var eventEmitter = new events.EventEmitter();

EventEmitter 对象如果在实例化时发生错误,会触发 error 事件。当添加新的监听器时,newListener 事件会触发,当监听器被移除时,removeListener 事件被触发。

下面我们用一个简单的例子说明 EventEmitter 的用法:

//event.js 文件var EventEmitter = require('events').EventEmitter; var event = new EventEmitter(); event.on('some_event', function() {     console.log('some_event 事件触发'); }); setTimeout(function() {     event.emit('some_event'); }, 1000); 

执行结果如下:

运行这段代码,1 秒后控制台输出了 'some_event 事件触发'。其原理是 event 对象注册了事件 some_event 的一个监听器,然后我们通过 setTimeout 在 1000 毫秒以后向 event 对象发送事件 some_event,此时会调用some_event 的监听器。

$ node event.js some_event 事件触发

EventEmitter 的每个事件由一个事件名和若干个参数组成,事件名是一个字符串,通常表达一定的语义。对于每个事件,EventEmitter 支持 若干个事件监听器。

当事件触发时,注册到这个事件的事件监听器被依次调用,事件参数作为回调函数参数传递。

让我们以下面的例子解释这个过程:

//event.js 文件var events = require('events'); var emitter = new events.EventEmitter(); emitter.on('someEvent', function(arg1, arg2) {     console.log('listener1', arg1, arg2); }); emitter.on('someEvent', function(arg1, arg2) {     console.log('listener2', arg1, arg2); }); emitter.emit('someEvent', 'arg1 参数', 'arg2 参数'); 

执行以上代码,运行的结果如下:

$ node event.js listener1 arg1 参数 arg2 参数listener2 arg1 参数 arg2 参数

以上例子中,emitter 为事件 someEvent 注册了两个事件监听器,然后触发了 someEvent 事件。

运行结果中可以看到两个事件监听器回调函数被先后调用。 这就是EventEmitter最简单的用法。

EventEmitter 提供了多个属性,如 on 和 emit。on 函数用于绑定事件函数,emit 属性用于触发一个事件。接下来我们来具体看下 EventEmitter 的属性介绍。

方法

序号方法 & 描述
1addListener(event, listener)
为指定事件添加一个监听器到监听器数组的尾部。
2on(event, listener)
为指定事件注册一个监听器,接受一个字符串 event 和一个回调函数。
server.on('connection', function (stream) {  console.log('someone connected!');});
3once(event, listener)
为指定事件注册一个单次监听器,即 监听器最多只会触发一次,触发后立刻解除该监听器。
server.once('connection', function (stream) {  console.log('Ah, we have our first user!');});
4removeListener(event, listener)

移除指定事件的某个监听器,监听器必须是该事件已经注册过的监听器。

它接受两个参数,第一个是事件名称,第二个是回调函数名称。

var callback = function(stream) {  console.log('someone connected!');};server.on('connection', callback);// ...server.removeListener('connection', callback);
5removeAllListeners([event])
移除所有事件的所有监听器, 如果指定事件,则移除指定事件的所有监听器。
6setMaxListeners(n)
默认情况下, EventEmitters 如果你添加的监听器超过 10 个就会输出警告信息。 setMaxListeners 函数用于改变监听器的默认限制的数量。
7listeners(event)
返回指定事件的监听器数组。
8emit(event, [arg1], [arg2], [...])
按监听器的顺序执行执行每个监听器,如果事件有注册监听返回 true,否则返回 false。

类方法

序号方法 & 描述
1listenerCount(emitter, event)
返回指定事件的监听器数量。
events.EventEmitter.listenerCount(emitter, eventName) //已废弃,不推荐events.emitter.listenerCount(eventName) //推荐

事件

序号事件 & 描述
1newListener
  • event - 字符串,事件名称

  • listener - 处理事件函数

该事件在添加新监听器时被触发。

2removeListener
  • event - 字符串,事件名称

  • listener - 处理事件函数

从指定监听器数组中删除一个监听器。需要注意的是,此操作将会改变处于被删监听器之后的那些监听器的索引。

实例

以下实例通过 connection(连接)事件演示了 EventEmitter 类的应用。

创建 main.js 文件,代码如下:

var events = require('events');var eventEmitter = new events.EventEmitter();// 监听器 #1var listener1 = function listener1() {   console.log('监听器 listener1 执行。');}// 监听器 #2var listener2 = function listener2() {  console.log('监听器 listener2 执行。');}// 绑定 connection 事件,处理函数为 listener1 eventEmitter.addListener('connection', listener1);// 绑定 connection 事件,处理函数为 listener2eventEmitter.on('connection', listener2);var eventListeners = eventEmitter.listenerCount('connection');console.log(eventListeners + " 个监听器监听连接事件。");// 处理 connection 事件 eventEmitter.emit('connection');// 移除监绑定的 listener1 函数eventEmitter.removeListener('connection', listener1);console.log("listener1 不再受监听。");// 触发连接事件eventEmitter.emit('connection');eventListeners = eventEmitter.listenerCount('connection');console.log(eventListeners + " 个监听器监听连接事件。");console.log("程序执行完毕。");

以上代码,执行结果如下所示:

$ node main.js2 个监听器监听连接事件。监听器 listener1 执行。监听器 listener2 执行。listener1 不再受监听。监听器 listener2 执行。1 个监听器监听连接事件。程序执行完毕。

error 事件

EventEmitter 定义了一个特殊的事件 error,它包含了错误的语义,我们在遇到 异常的时候通常会触发 error 事件。

当 error 被触发时,EventEmitter 规定如果没有响 应的监听器,Node.js 会把它当作异常,退出程序并输出错误信息。

我们一般要为会触发 error 事件的对象设置监听器,避免遇到错误后整个程序崩溃。例如:

var events = require('events'); var emitter = new events.EventEmitter(); emitter.emit('error'); 

运行时会显示以下错误:

node.js:201 throw e; // process.nextTick error, or 'error' event on first tick ^ Error: Uncaught, unspecified 'error' event. at EventEmitter.emit (events.js:50:15) at Object.<anonymous> (/home/byvoid/error.js:5:9) 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) 

继承 EventEmitter

大多数时候我们不会直接使用 EventEmitter,而是在对象中继承它。包括 fs、net、 http 在内的,只要是支持事件响应的核心模块都是 EventEmitter 的子类。

为什么要这样做呢?原因有两点:

首先,具有某个实体功能的对象实现事件符合语义, 事件的监听和发生应该是一个对象的方法。

其次 JavaScript 的对象机制是基于原型的,支持 部分多重继承,继承 EventEmitter 不会打乱对象原有的继承关系。


JavaScript 语言自身只有字符串数据类型,没有二进制数据类型。

但在处理像TCP流或文件流时,必须使用到二进制数据。因此在 Node.js中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。

在 Node.js 中,Buffer 类是随 Node 内核一起发布的核心库。Buffer 库为 Node.js 带来了一种存储原始数据的方法,可以让 Node.js 处理二进制数据,每当需要在 Node.js 中处理I/O操作中移动的数据时,就有可能使用 Buffer 库。原始数据存储在 Buffer 类的实例中。一个 Buffer 类似于一个整数数组,但它对应于 V8 堆内存之外的一块原始内存。

 在v6.0之前创建Buffer对象直接使用new Buffer()构造函数来创建对象实例,但是Buffer对内存的权限操作相比很大,可以直接捕获一些敏感信息,所以在v6.0以后,官方文档里面建议使用 Buffer.from() 接口去创建Buffer对象。


Buffer 与字符编码

Buffer 实例一般用于表示编码字符的序列,比如 UTF-8 、 UCS2 、 Base64 、或十六进制编码的数据。 通过使用显式的字符编码,就可以在 Buffer 实例与普通的 JavaScript 字符串之间进行相互转换。

const buf = Buffer.from('51coolma', 'ascii');
// 输出 72756e6f6f62console.log(buf.toString('hex'));// 输出 cnVub29iconsole.log(buf.toString('base64'));

Node.js 目前支持的字符编码包括:

  • ascii - 仅支持 7 位 ASCII 数据。如果设置去掉高位的话,这种编码是非常快的。
  • utf8 - 多字节编码的 Unicode 字符。许多网页和其他文档格式都使用 UTF-8 。
  • utf16le - 2 或 4 个字节,小字节序编码的 Unicode 字符。支持代理对(U+10000 至 U+10FFFF)。
  • ucs2 - utf16le 的别名。
  • base64 - Base64 编码。
  • latin1 - 一种把 Buffer 编码成一字节编码的字符串的方式。
  • binary - latin1 的别名。
  • hex - 将每个字节编码为两个十六进制字符。

创建 Buffer 类

Buffer 提供了以下 API 来创建 Buffer 类:

  • Buffer.alloc(size[, fill[, encoding]]): 返回一个指定大小的 Buffer 实例,如果没有设置 fill,则默认填满 0
  • Buffer.allocUnsafe(size): 返回一个指定大小的 Buffer 实例,但是它不会被初始化,所以它可能包含敏感的数据
  • Buffer.allocUnsafeSlow(size)
  • Buffer.from(array): 返回一个被 array 的值初始化的新的 Buffer 实例(传入的 array 的元素只能是数字,不然就会自动被 0 覆盖)
  • Buffer.from(arrayBuffer[, byteOffset[, length]]): 返回一个新建的与给定的 ArrayBuffer 共享同一内存的 Buffer。
  • Buffer.from(buffer): 复制传入的 Buffer 实例的数据,并返回一个新的 Buffer 实例
  • Buffer.from(string[, encoding]): 返回一个被 string 的值初始化的新的 Buffer 实例
// 创建一个长度为 10、且用 0 填充的 Buffer。const buf1 = Buffer.alloc(10);// 创建一个长度为 10、且用 0x1 填充的 Buffer。 const buf2 = Buffer.alloc(10, 1);// 创建一个长度为 10、且未初始化的 Buffer。// 这个方法比调用 Buffer.alloc() 更快,// 但返回的 Buffer 实例可能包含旧数据,// 因此需要使用 fill() 或 write() 重写。const buf3 = Buffer.allocUnsafe(10);// 创建一个包含 [0x1, 0x2, 0x3] 的 Buffer。const buf4 = Buffer.from([1, 2, 3]);// 创建一个包含 UTF-8 字节 [0x74, 0xc3, 0xa9, 0x73, 0x74] 的 Buffer。const buf5 = Buffer.from('tést');// 创建一个包含 Latin-1 字节 [0x74, 0xe9, 0x73, 0x74] 的 Buffer。const buf6 = Buffer.from('tést', 'latin1');

写入缓冲区

语法

写入 Node 缓冲区的语法如下所示:

buf.write(string[, offset[, length]][, encoding])

参数

参数描述如下:

  • string - 写入缓冲区的字符串。
  • offset - 缓冲区开始写入的索引值,默认为 0 。
  • length - 写入的字节数,默认为 buffer.length
  • encoding - 使用的编码。默认为 'utf8' 。

根据 encoding 的字符编码写入 string 到 buf 中的 offset 位置。 length 参数是写入的字节数。 如果 buf 没有足够的空间保存整个字符串,则只会写入 string 的一部分。 只部分解码的字符不会被写入。

返回值

返回实际写入的大小。如果 buffer 空间不足, 则只会写入部分字符串。

实例

buf = Buffer.alloc(256);len = buf.write("www.51coolma.cn");
console.log("写入字节数 : "+ len);

执行以上代码,输出结果为:

$node main.js写入字节数 : 16

从缓冲区读取数据

语法

读取 Node 缓冲区数据的语法如下所示:

buf.toString([encoding[, start[, end]]])

参数

参数描述如下:

  • encoding - 使用的编码。默认为 'utf8' 。
  • start - 指定开始读取的索引位置,默认为 0。
  • end - 结束位置,默认为缓冲区的末尾。

返回值

解码缓冲区数据并使用指定的编码返回字符串。

实例

buf = Buffer.alloc(26);for (var i = 0 ; i < 26 ; i++) {  buf[i] = i + 97;}console.log( buf.toString('ascii'));       // 输出: abcdefghijklmnopqrstuvwxyzconsole.log( buf.toString('ascii',0,5));   //使用 'ascii' 编码, 并输出: abcdeconsole.log( buf.toString('utf8',0,5));    // 使用 'utf8' 编码, 并输出: abcdeconsole.log( buf.toString(undefined,0,5)); // 使用默认的 'utf8' 编码, 并输出: abcde

执行以上代码,输出结果为:

$ node main.jsabcdefghijklmnopqrstuvwxyzabcdeabcdeabcde

将 Buffer 转换为 JSON 对象

语法

将 Node Buffer 转换为 JSON 对象的函数语法格式如下:

buf.toJSON()

当字符串化一个 Buffer 实例时,JSON.stringify() 会隐式地调用该 toJSON()。

返回值

返回 JSON 对象。

实例

const buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5]);const json = JSON.stringify(buf);// 输出: {"type":"Buffer","data":[1,2,3,4,5]}console.log(json);const copy = JSON.parse(json, (key, value) => {  return value && value.type === 'Buffer' ?    Buffer.from(value.data) :    value;});// 输出: <Buffer 01 02 03 04 05>console.log(copy);

执行以上代码,输出结果为:

{"type":"Buffer","data":[1,2,3,4,5]}<Buffer 01 02 03 04 05>

缓冲区合并

语法

Node 缓冲区合并的语法如下所示:

Buffer.concat(list[, totalLength])

参数

参数描述如下:

  • list - 用于合并的 Buffer 对象数组列表。
  • totalLength - 指定合并后Buffer对象的总长度。

返回值

返回一个多个成员合并的新 Buffer 对象。

实例

var buffer1 = Buffer.from(('51coolma编程狮'));
var buffer2 = Buffer.from(('www.51coolma.cn'));
var buffer3 = Buffer.concat([buffer1,buffer2]);console.log("buffer3 内容: " + buffer3.toString());

执行以上代码,输出结果为:

buffer3 内容: 编程狮www.51coolma.cn

缓冲区比较

语法

Node Buffer 比较的函数语法如下所示, 该方法在 Node.js v0.12.2 版本引入:

buf.compare(otherBuffer);

参数

参数描述如下:

  • otherBuffer - 与 buf 对象比较的另外一个 Buffer 对象。

返回值

返回一个数字,表示 buf 在 otherBuffer 之前,之后或相同。

实例

var buffer1 = Buffer.from('ABC');var buffer2 = Buffer.from('ABCD');var result = buffer1.compare(buffer2);if(result < 0) {   console.log(buffer1 + " 在 " + buffer2 + "之前");}else if(result == 0){   console.log(buffer1 + " 与 " + buffer2 + "相同");}else {   console.log(buffer1 + " 在 " + buffer2 + "之后");}

执行以上代码,输出结果为:

ABC在ABCD之前

拷贝缓冲区

语法

Node 缓冲区拷贝语法如下所示:

buf.copy(targetBuffer[, targetStart[, sourceStart[, sourceEnd]]])

参数

参数描述如下:

  • targetBuffer - 要拷贝的 Buffer 对象。
  • targetStart - 数字, 可选, 默认: 0
  • sourceStart - 数字, 可选, 默认: 0
  • sourceEnd - 数字, 可选, 默认: buffer.length

返回值

没有返回值。

实例

var buf1 = Buffer.from('abcdefghijkl');var buf2 = Buffer.from('W3CSCHOOL');//将 buf2 插入到 buf1 指定位置上buf2.copy(buf1, 2);console.log(buf1.toString());

执行以上代码,输出结果为:

abW3CSCHOOLijkl

缓冲区裁剪

Node 缓冲区裁剪语法如下所示:

buf.slice([start[, end]])

参数

参数描述如下:

  • start - 数字, 可选, 默认: 0
  • end - 数字, 可选, 默认: buffer.length

返回值

返回一个新的缓冲区,它和旧缓冲区指向同一块内存,但是从索引 start 到 end 的位置剪切。

实例

var buffer1 = Buffer.from('51coolma');// 剪切缓冲区var buffer2 = buffer1.slice(0,2);console.log("buffer2 content: " + buffer2.toString());

执行以上代码,输出结果为:

buffer2 content: w3

缓冲区长度

语法

Node 缓冲区长度计算语法如下所示:

buf.length;

返回值

返回 Buffer 对象所占据的内存长度。

实例

var buffer = Buffer.from('www.51coolma.cn');
// 缓冲区长度console.log("buffer length: " + buffer.length);

执行以上代码,输出结果为:

buffer length: 16

方法参考手册

以下列出了 Node.js Buffer 模块常用的方法(注意有些方法在旧版本是没有的):

序号方法 & 描述
1new Buffer(size)
分配一个新的 size 大小单位为8位字节的 buffer。 注意, size 必须小于 kMaxLength,否则,将会抛出异常 RangeError。废弃的: 使用 Buffer.alloc() 代替(或 Buffer.allocUnsafe())。
2new Buffer(buffer)
拷贝参数 buffer 的数据到 Buffer 实例。废弃的: 使用 Buffer.from(buffer) 代替。
3new Buffer(str[, encoding])
分配一个新的 buffer ,其中包含着传入的 str 字符串。 encoding 编码方式默认为 'utf8'。 废弃的: 使用 Buffer.from(string[, encoding]) 代替。
4buf.length
返回这个 buffer 的 bytes 数。注意这未必是 buffer 里面内容的大小。length 是 buffer 对象所分配的内存数,它不会随着这个 buffer 对象内容的改变而改变。
5buf.write(string[, offset[, length]][, encoding])
根据参数 offset 偏移量和指定的 encoding 编码方式,将参数 string 数据写入buffer。 offset 偏移量默认值是 0, encoding 编码方式默认是 utf8。 length 长度是将要写入的字符串的 bytes 大小。 返回 number 类型,表示写入了多少 8 位字节流。如果 buffer 没有足够的空间来放整个 string,它将只会只写入部分字符串。 length 默认是 buffer.length - offset。 这个方法不会出现写入部分字符。
6buf.writeUIntLE(value, offset, byteLength[, noAssert])
将 value 写入到 buffer 里, 它由 offset 和 byteLength 决定,最高支持 48 位无符号整数,小端对齐,例如:
const buf = Buffer.allocUnsafe(6);buf.writeUIntLE(0x1234567890ab, 0, 6);// 输出: <Buffer ab 90 78 56 34 12>console.log(buf);
noAssert 值为 true 时,不再验证 value 和 offset 的有效性。 默认是 false。
7buf.writeUIntBE(value, offset, byteLength[, noAssert])
将 value 写入到 buffer 里, 它由 offset 和 byteLength 决定,最高支持 48 位无符号整数,大端对齐。noAssert 值为 true 时,不再验证 value 和 offset 的有效性。 默认是 false。
const buf = Buffer.allocUnsafe(6);buf.writeUIntBE(0x1234567890ab, 0, 6);// 输出: <Buffer 12 34 56 78 90 ab>console.log(buf);
8buf.writeIntLE(value, offset, byteLength[, noAssert])
将value 写入到 buffer 里, 它由offset 和 byteLength 决定,最高支持48位有符号整数,小端对齐。noAssert 值为 true 时,不再验证 value 和 offset 的有效性。 默认是 false。
9buf.writeIntBE(value, offset, byteLength[, noAssert])
将value 写入到 buffer 里, 它由offset 和 byteLength 决定,最高支持48位有符号整数,大端对齐。noAssert 值为 true 时,不再验证 value 和 offset 的有效性。 默认是 false。
10buf.readUIntLE(offset, byteLength[, noAssert])
支持读取 48 位以下的无符号数字,小端对齐。noAssert 值为 true 时, offset 不再验证是否超过 buffer 的长度,默认为 false。
11buf.readUIntBE(offset, byteLength[, noAssert])
支持读取 48 位以下的无符号数字,大端对齐。noAssert 值为 true 时, offset 不再验证是否超过 buffer 的长度,默认为 false。
12buf.readIntLE(offset, byteLength[, noAssert])
支持读取 48 位以下的有符号数字,小端对齐。noAssert 值为 true 时, offset 不再验证是否超过 buffer 的长度,默认为 false。
13buf.readIntBE(offset, byteLength[, noAssert])
支持读取 48 位以下的有符号数字,大端对齐。noAssert 值为 true 时, offset 不再验证是否超过 buffer 的长度,默认为 false。
14buf.toString([encoding[, start[, end]]])
根据 encoding 参数(默认是 'utf8')返回一个解码过的 string 类型。还会根据传入的参数 start (默认是 0) 和 end (默认是 buffer.length)作为取值范围。
15buf.toJSON()
将 Buffer 实例转换为 JSON 对象。
16buf[index]
获取或设置指定的字节。返回值代表一个字节,所以返回值的合法范围是十六进制0x00到0xFF 或者十进制0至 255。
17buf.equals(otherBuffer)
比较两个缓冲区是否相等,如果是返回 true,否则返回 false。
18buf.compare(otherBuffer)
比较两个 Buffer 对象,返回一个数字,表示 buf 在 otherBuffer 之前,之后或相同。
19buf.copy(targetBuffer[, targetStart[, sourceStart[, sourceEnd]]])
buffer 拷贝,源和目标可以相同。 targetStart 目标开始偏移和 sourceStart 源开始偏移默认都是 0。 sourceEnd 源结束位置偏移默认是源的长度 buffer.length 。
20buf.slice([start[, end]])
剪切 Buffer 对象,根据 start(默认是 0 ) 和 end (默认是 buffer.length ) 偏移和裁剪了索引。 负的索引是从 buffer 尾部开始计算的。
21buf.readUInt8(offset[, noAssert])
根据指定的偏移量,读取一个无符号 8 位整数。若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 如果这样 offset 可能会超出buffer 的末尾。默认是 false。
22buf.readUInt16LE(offset[, noAssert])
根据指定的偏移量,使用特殊的 endian 字节序格式读取一个无符号 16 位整数。若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出 buffer 的末尾。默认是 false。
23buf.readUInt16BE(offset[, noAssert])
根据指定的偏移量,使用特殊的 endian 字节序格式读取一个无符号 16 位整数,大端对齐。若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出 buffer 的末尾。默认是 false。
24buf.readUInt32LE(offset[, noAssert])
根据指定的偏移量,使用指定的 endian 字节序格式读取一个无符号 32 位整数,小端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer 的末尾。默认是 false。
25buf.readUInt32BE(offset[, noAssert])
根据指定的偏移量,使用指定的 endian 字节序格式读取一个无符号 32 位整数,大端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer 的末尾。默认是 false。
26buf.readInt8(offset[, noAssert])
根据指定的偏移量,读取一个有符号 8 位整数。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出 buffer 的末尾。默认是 false。
27buf.readInt16LE(offset[, noAssert])
根据指定的偏移量,使用特殊的 endian 格式读取一个 有符号 16 位整数,小端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出 buffer 的末尾。默认是 false。
28buf.readInt16BE(offset[, noAssert])
根据指定的偏移量,使用特殊的 endian 格式读取一个 有符号 16 位整数,大端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出 buffer 的末尾。默认是 false。
29buf.readInt32LE(offset[, noAssert])
根据指定的偏移量,使用指定的 endian 字节序格式读取一个有符号 32 位整数,小端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer 的末尾。默认是 false。
30buf.readInt32BE(offset[, noAssert])
根据指定的偏移量,使用指定的 endian 字节序格式读取一个有符号 32 位整数,大端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer 的末尾。默认是 false。
31buf.readFloatLE(offset[, noAssert])
根据指定的偏移量,使用指定的 endian 字节序格式读取一个 32 位双浮点数,小端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer的末尾。默认是 false。
32buf.readFloatBE(offset[, noAssert])
根据指定的偏移量,使用指定的 endian 字节序格式读取一个 32 位双浮点数,大端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer的末尾。默认是 false。
33buf.readDoubleLE(offset[, noAssert])
根据指定的偏移量,使用指定的 endian字节序格式读取一个 64 位双精度数,小端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer 的末尾。默认是 false。
34buf.readDoubleBE(offset[, noAssert])
根据指定的偏移量,使用指定的 endian字节序格式读取一个 64 位双精度数,大端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer 的末尾。默认是 false。
35buf.writeUInt8(value, offset[, noAssert])
根据传入的 offset 偏移量将 value 写入 buffer。注意:value 必须是一个合法的无符号 8 位整数。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出 buffer 的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则不要使用。默认是 false。
36buf.writeUInt16LE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个合法的无符号 16 位整数,小端对齐。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出buffer的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false。
37buf.writeUInt16BE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个合法的无符号 16 位整数,大端对齐。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出buffer的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false。
38buf.writeUInt32LE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式(LITTLE-ENDIAN:小字节序)将 value 写入buffer。注意:value 必须是一个合法的无符号 32 位整数,小端对齐。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着value 可能过大,或者offset可能会超出buffer的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false。
39buf.writeUInt32BE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式(Big-Endian:大字节序)将 value 写入buffer。注意:value 必须是一个合法的有符号 32 位整数。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者offset可能会超出buffer的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false。
40buf.writeInt8(value, offset[, noAssert])
41buf.writeInt16LE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个合法的 signed 16 位整数。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出 buffer 的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false 。
42buf.writeInt16BE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个合法的 signed 16 位整数。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出 buffer 的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false 。
43buf.writeInt32LE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个合法的 signed 32 位整数。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出 buffer 的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false。
44buf.writeInt32BE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个合法的 signed 32 位整数。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出 buffer 的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false。
45buf.writeFloatLE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer 。注意:当 value 不是一个 32 位浮点数类型的值时,结果将是不确定的。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value可能过大,或者 offset 可能会超出 buffer 的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false。
46buf.writeFloatBE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer 。注意:当 value 不是一个 32 位浮点数类型的值时,结果将是不确定的。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value可能过大,或者 offset 可能会超出 buffer 的末尾从而造成 value 被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false。
47buf.writeDoubleLE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个有效的 64 位double 类型的值。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出 buffer 的末尾从而造成value被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false。
48buf.writeDoubleBE(value, offset[, noAssert])
根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个有效的 64 位double 类型的值。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出 buffer 的末尾从而造成value被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是 false。
49buf.fill(value[, offset][, end])
使用指定的 value 来填充这个 buffer。如果没有指定 offset (默认是 0) 并且 end (默认是 buffer.length) ,将会填充整个buffer。


Stream 是 Node.js 中非常重要的一个模块,应用广泛。

Stream 是一个抽象接口,Node 中有很多对象实现了这个接口。例如,对http 服务器发起请求的request 对象就是一个 Stream,还有stdout(标准输出)。

该抽象接口是可读、可写或是既可读又可写的,通过这些接口,我们可以和磁盘文件、套接字、HTTP请求来交互,实现数据从一个地方流动到另一个地方的功能。

Node.js,Stream 有四种流类型:

  • Readable - 可读操作。

  • Writable - 可写操作。

  • Duplex - 可读可写操作.

  • Transform - 操作被写入数据,然后读出结果。

所有的 Stream 对象都是 EventEmitter 的实例。常用的事件有:

  • data - 当有数据可读时触发。

  • end - 没有更多的数据可读时触发。

  • error - 在接收和写入过程中发生错误时触发。

  • finish - 所有数据已被写入到底层系统时触发。

本教程会为大家介绍常用的流操作。


从流中读取数据

创建 input.txt 文件,内容如下:

W3Cschool教程官网地址:www.51coolma.cn

创建 main.js 文件, 代码如下:

var fs = require("fs");var data = '';// 创建可读流var readerStream = fs.createReadStream('input.txt');// 设置编码为 utf8。readerStream.setEncoding('UTF8');// 处理流事件 --> data, end, and errorreaderStream.on('data', function(chunk) {   data += chunk;});readerStream.on('end',function(){   console.log(data);});readerStream.on('error', function(err){   console.log(err.stack);});console.log("程序执行完毕");

以上代码执行结果如下:

程序执行完毕W3Cschool教程官网地址:www.51coolma.cn

写入流

创建 main.js 文件, 代码如下:

var fs = require("fs");var data = 'W3Cschool教程官网地址:www.51coolma.cn';// 创建一个可以写入的流,写入到文件 output.txt 中var writerStream = fs.createWriteStream('output.txt');// 使用 utf8 编码写入数据writerStream.write(data,'UTF8');// 标记文件末尾writerStream.end();// 处理流事件 --> data, end, and errorwriterStream.on('finish', function() {    console.log("写入完成。");});writerStream.on('error', function(err){   console.log(err.stack);});console.log("程序执行完毕");

以上程序会将 data 变量的数据写入到 output.txt 文件中。代码执行结果如下:

$ node main.js 程序执行完毕写入完成。

查看 output.txt 文件的内容:

$ cat output.txt W3Cschool教程官网地址:www.51coolma.cn

管道流

管道提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另外一个流中。

如上面的图片所示,我们把文件比作装水的桶,而水就是文件里的内容,我们用一根管子(pipe)连接两个桶使得水从一个桶流入另一个桶,这样就慢慢的实现了大文件的复制过程。

以下实例我们通过读取一个文件内容并将内容写入到另外一个文件中。

设置 input.txt 文件内容如下:

W3Cschool教程官网地址:www.51coolma.cn管道流操作实例

创建 main.js 文件, 代码如下:

var fs = require("fs");// 创建一个可读流var readerStream = fs.createReadStream('input.txt');// 创建一个可写流var writerStream = fs.createWriteStream('output.txt');// 管道读写操作// 读取 input.txt 文件内容,并将内容写入到 output.txt 文件中readerStream.pipe(writerStream);console.log("程序执行完毕");

代码执行结果如下:

$ node main.js 程序执行完毕

查看 output.txt 文件的内容:

$ cat output.txt W3Cschool教程官网地址:www.51coolma.cn管道流操作实例

链式流

链式是通过连接输出流到另外一个流并创建多个对个流操作链的机制。链式流一般用于管道操作。

接下来我们就是用管道和链式来压缩和解压文件。

创建 compress.js 文件, 代码如下:

var fs = require("fs");var zlib = require('zlib');// 压缩 input.txt 文件为 input.txt.gzfs.createReadStream('input.txt')  .pipe(zlib.createGzip())  .pipe(fs.createWriteStream('input.txt.gz'));  console.log("文件压缩完成。");

代码执行结果如下:

$ node compress.js 文件压缩完成。

执行完以上操作后,我们可以看到当前目录下生成了 input.txt 的压缩文件 input.txt.gz。

接下来,让我们来解压该文件,创建 decompress.js 文件,代码如下:

var fs = require("fs");var zlib = require('zlib');// 解压 input.txt.gz 文件为 input.txtfs.createReadStream('input.txt.gz')  .pipe(zlib.createGunzip())  .pipe(fs.createWriteStream('input.txt'));  console.log("文件解压完成。");

代码执行结果如下:

$ node decompress.js 文件解压完成。


为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统。

模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个 Node.js 文件就是一个模块,这个文件可能是JavaScript 代码、JSON 或者编译过的C/C++ 扩展。

创建模块

在 Node.js 中,创建一个模块非常简单,如下我们创建一个 'main.js' 文件,代码如下:

var hello = require('./hello');hello.world();

以上实例中,代码 require('./hello') 引入了当前目录下的hello.js文件(./ 为当前目录,node.js默认后缀为js)。

Node.js 提供了exports 和 require 两个对象,其中 exports 是模块公开的接口,require 用于从外部获取一个模块的接口,即所获取模块的 exports 对象。

接下来我们就来创建hello.js文件,代码如下:

exports.world = function() {  console.log('Hello World');}

在以上示例中,hello.js 通过 exports 对象把 world 作为模块的访 问接口,在 main.js 中通过 require('./hello') 加载这个模块,然后就可以直接访 问hello.js 中 exports 对象的成员函数了。

有时候我们只是想把一个对象封装到模块中,格式如下:

module.exports = function() {  // ...}

例如:

//hello.js function Hello() {  var name;     this.setName = function(thyName) {        name = thyName;   };    this.sayHello = function() {      console.log('Hello ' + name);   }; }; module.exports = Hello;

这样就可以直接获得这个对象了:

//main.js var Hello = require('./hello'); hello = new Hello(); hello.setName('BYVoid'); hello.sayHello(); 

模块接口的唯一变化是使用 module.exports = Hello 代替了exports.world = function(){}。 在外部引用该模块时,其接口对象就是要输出的 Hello 对象本身,而不是原先的 exports。


服务端的模块放在哪里

也许你已经注意到,我们已经在代码中使用了模块了。像这样:

var http = require("http");...http.createServer(...);

Node.js中自带了一个叫做"http"的模块,我们在我们的代码中请求它并把返回值赋给一个本地变量。

这把我们的本地变量变成了一个拥有所有 http 模块所提供的公共方法的对象。

Node.js 的 require方法中的文件查找策略如下:

由于Node.js中存在4类模块(原生模块和3种文件模块),尽管require方法极其简单,但是内部的加载却是十分复杂的,其加载优先级也各自不同。如下图所示:

nodejs-require

从文件模块缓存中加载

尽管原生模块与文件模块的优先级不同,但是都不会优先于从文件模块的缓存中加载已经存在的模块。

从原生模块加载

原生模块的优先级仅次于文件模块缓存的优先级。require 方法在解析文件名之后,优先检查模块是否在原生模块列表中。以 http 模块为例,尽管在目录下存在一个http/http.js/http.node/http.json文件,require("http") 都不会从这些文件中加载,而是从原生模块中加载。

原生模块也有一个缓存区,同样也是优先从缓存区加载。如果缓存区没有被加载过,则调用原生模块的加载方式进行加载和执行。

从文件加载

当文件模块缓存中不存在,而且不是原生模块的时候,Node.js会解析require方法传入的参数,并从文件系统中加载实际的文件,加载过程中的包装和编译细节在前一节中已经介绍过,这里我们将详细描述查找文件模块的过程,其中,也有一些细节值得知晓。

require方法接受以下几种参数的传递:

  • http、fs、path等,原生模块。
  • ./mod或../mod,相对路径的文件模块。
  • /pathtomodule/mod,绝对路径的文件模块。
  • mod,非原生模块的文件模块。

在路径 Y 下执行 require(X) 语句执行顺序:

1. 如果 X 是内置模块   a. 返回内置模块   b. 停止执行2. 如果 X 以 '/' 开头   a. 设置 Y 为文件根路径3. 如果 X 以 './' 或 '/' or '../' 开头   a. LOAD_AS_FILE(Y + X)   b. LOAD_AS_DIRECTORY(Y + X)4. LOAD_NODE_MODULES(X, dirname(Y))5. 抛出异常 "not found"LOAD_AS_FILE(X)1. 如果 X 是一个文件, 将 X 作为 JavaScript 文本载入并停止执行。2. 如果 X.js 是一个文件, 将 X.js 作为 JavaScript 文本载入并停止执行。3. 如果 X.json 是一个文件, 解析 X.json 为 JavaScript 对象并停止执行。4. 如果 X.node 是一个文件, 将 X.node 作为二进制插件载入并停止执行。LOAD_INDEX(X)1. 如果 X/index.js 是一个文件,  将 X/index.js 作为 JavaScript 文本载入并停止执行。2. 如果 X/index.json 是一个文件, 解析 X/index.json 为 JavaScript 对象并停止执行。3. 如果 X/index.node 是一个文件,  将 X/index.node 作为二进制插件载入并停止执行。LOAD_AS_DIRECTORY(X)1. 如果 X/package.json 是一个文件,   a. 解析 X/package.json, 并查找 "main" 字段。   b. let M = X + (json main 字段)   c. LOAD_AS_FILE(M)   d. LOAD_INDEX(M)2. LOAD_INDEX(X)LOAD_NODE_MODULES(X, START)1. let DIRS=NODE_MODULES_PATHS(START)2. for each DIR in DIRS:   a. LOAD_AS_FILE(DIR/X)   b. LOAD_AS_DIRECTORY(DIR/X)NODE_MODULES_PATHS(START)1. let PARTS = path split(START)2. let I = count of PARTS - 13. let DIRS = []4. while I >= 0,   a. if PARTS[I] = "node_modules" CONTINUE   b. DIR = path join(PARTS[0 .. I] + "node_modules")   c. DIRS = DIRS + DIR   d. let I = I - 15. return DIRS
exports 和 module.exports 的使用如果要对外暴露属性或方法,就用 exports 就行,要暴露对象(类似class,包含了很多属性和方法),就用 module.exports。


在JavaScript中,一个函数可以作为另一个函数接收一个参数。我们可以先定义一个函数,然后传递,也可以在传递参数的地方直接定义函数。

Node.js中函数的使用与Javascript类似,举例来说,你可以这样做:

function say(word) {  console.log(word);}function execute(someFunction, value) {  someFunction(value);}execute(say, "Hello");

以上代码中,我们把 say 函数作为execute函数的第一个变量进行了传递。这里返回的不是 say 的返回值,而是 say 本身!

这样一来, say 就变成了execute 中的本地变量 someFunction ,execute可以通过调用 someFunction() (带括号的形式)来使用 say 函数。

当然,因为 say 有一个变量, execute 在调用 someFunction 时可以传递这样一个变量。


匿名函数

我们可以把一个函数作为变量传递。但是我们不一定要绕这个"先定义,再传递"的圈子,我们可以直接在另一个函数的括号中定义和传递这个函数:

function execute(someFunction, value) {  someFunction(value);}execute(function(word){ console.log(word) }, "Hello");

我们在 execute 接受第一个参数的地方直接定义了我们准备传递给 execute 的函数。

用这种方式,我们甚至不用给这个函数起名字,这也是为什么它被叫做匿名函数 。


函数传递是如何让HTTP服务器工作的

带着这些知识,我们再来看看我们简约而不简单的HTTP服务器:

var http = require("http");http.createServer(function(request, response) {  response.writeHead(200, {"Content-Type": "text/plain"});  response.write("Hello World");  response.end();}).listen(8888);

现在它看上去应该清晰了很多:我们向 createServer 函数传递了一个匿名函数。

用这样的代码也可以达到同样的目的:

var http = require("http");function onRequest(request, response) {  response.writeHead(200, {"Content-Type": "text/plain"});  response.write("Hello World");  response.end();}http.createServer(onRequest).listen(8888);


我们要为路由提供请求的URL和其他需要的GET及POST参数,随后路由需要根据这些数据来执行相应的代码。

因此,我们需要查看HTTP请求,从中提取出请求的URL以及GET/POST参数。这一功能应当属于路由还是服务器(甚至作为一个模块自身的功能)确实值得探讨,但这里暂定其为我们的HTTP服务器的功能。

我们需要的所有数据都会包含在request对象中,该对象作为onRequest()回调函数的第一个参数传递。但是为了解析这些数据,我们需要额外的Node.JS模块,它们分别是url和querystring模块。

                   url.parse(string).query                                           |           url.parse(string).pathname      |                       |                   |                       |                   |                     ------ -------------------http://localhost:8888/start?foo=bar&hello=world                                ---       -----                                 |          |                                 |          |              querystring(string)["foo"]    |                                            |                         querystring(string)["hello"]

当然我们也可以用querystring模块来解析POST请求体中的参数,稍后会有演示。

现在我们来给onRequest()函数加上一些逻辑,用来找出浏览器请求的URL路径:

var http = require("http");var url = require("url");function start() {  function onRequest(request, response) {    var pathname = url.parse(request.url).pathname;    console.log("Request for " + pathname + " received.");    response.writeHead(200, {"Content-Type": "text/plain"});    response.write("Hello World");    response.end();  }  http.createServer(onRequest).listen(8888);  console.log("Server has started.");}exports.start = start;

好了,我们的应用现在可以通过请求的URL路径来区别不同请求了--这使我们得以使用路由(还未完成)来将请求以URL路径为基准映射到处理程序上。

在我们所要构建的应用中,这意味着来自/start和/upload的请求可以使用不同的代码来处理。稍后我们将看到这些内容是如何整合到一起的。

现在我们可以来编写路由了,建立一个名为router.js的文件,添加以下内容:

function route(pathname) {  console.log("About to route a request for " + pathname);}exports.route = route;

如你所见,这段代码什么也没干,不过对于现在来说这是应该的。在添加更多的逻辑以前,我们先来看看如何把路由和服务器整合起来。

我们的服务器应当知道路由的存在并加以有效利用。我们当然可以通过硬编码的方式将这一依赖项绑定到服务器上,但是其它语言的编程经验告诉我们这会是一件非常痛苦的事,因此我们将使用依赖注入的方式较松散地添加路由模块。

首先,我们来扩展一下服务器的 start() 函数,以便将路由函数作为参数传递过去,server.js 文件代码如下

var http = require("http");var url = require("url"); function start(route) {  function onRequest(request, response) {    var pathname = url.parse(request.url).pathname;    console.log("Request for " + pathname + " received.");     route(pathname);     response.writeHead(200, {"Content-Type": "text/plain"});    response.write("Hello World");    response.end();  }   http.createServer(onRequest).listen(8888);  console.log("Server has started.");} exports.start = start;

同时,我们会相应扩展index.js,使得路由函数可以被注入到服务器中:

var server = require("./server");var router = require("./router");server.start(router.route);

在这里,我们传递的函数依旧什么也没做。

如果现在启动应用(node index.js,始终记得这个命令行),随后请求一个URL,你将会看到应用输出相应的信息,这表明我们的HTTP服务器已经在使用路由模块了,并会将请求的路径传递给路由:

bash$ node index.jsRequest for /foo received.About to route a request for /foo

以上输出已经去掉了比较烦人的/favicon.ico请求相关的部分。

浏览器访问 http://127.0.0.1:8888/,输出结果如下:


JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可以在程序的任何地方访问,即全局变量。

在浏览器 JavaScript 中,通常 window 是全局对象, 而 Node.js 中的全局对象是 global,所有全局变量(除了 global 本身以外)都是 global 对象的属性。

在 Node.js 我们可以直接访问到 global 的属性,而不需要在应用中包含它。


全局对象与全局变量

global 最根本的作用是作为全局变量的宿主。按照 ECMAScript 的定义,满足以下条件的变量是全局变量:

  • 在最外层定义的变量;
  • 全局对象的属性;
  • 隐式定义的变量(未定义直接赋值的变量)。

当你定义一个全局变量时,这个变量同时也会成为全局对象的属性,反之亦然。需要注 意的是,在Node.js 中你不可能在最外层定义变量,因为所有用户代码都是属于当前模块的, 而模块本身不是最外层上下文。

注意: 永远使用var 定义变量以避免引入全局变量,因为全局变量会污染 命名空间,提高代码的耦合风险。


__filename

__filename 表示当前正在执行的脚本的文件名。它将输出文件所在位置的绝对路径,且和命令行参数所指定的文件名不一定相同。 如果在模块中,返回的值是模块文件的路径。

实例

创建文件 main.js ,代码如下所示:

// 输出全局变量 __filename 的值console.log( __filename );

执行 main.js 文件,代码如下所示:

$ node main.js/web/com/51coolma/nodejs/main.js

__dirname

__dirname 表示当前执行脚本所在的目录。

实例

创建文件 main.js ,代码如下所示:

// 输出全局变量 __dirname 的值console.log( __dirname );

执行 main.js 文件,代码如下所示:

$ node main.js/web/com/51coolma/nodejs

setTimeout(cb, ms)

setTimeout(cb, ms) 全局函数在指定的毫秒(ms)数后执行指定函数(cb)。:setTimeout() 只执行一次指定函数。

返回一个代表定时器的句柄值。

实例

创建文件 main.js ,代码如下所示:

function printHello(){   console.log( "Hello, World!");}// 两秒后执行以上函数setTimeout(printHello, 2000);

执行 main.js 文件,代码如下所示:

$ node main.jsHello, World!

clearTimeout(t)

clearTimeout( t ) 全局函数用于停止一个之前通过 setTimeout() 创建的定时器。 参数 t 是通过 setTimeout() 函数创建的定时器。

实例

创建文件 main.js ,代码如下所示:

function printHello(){   console.log( "Hello, World!");}// 两秒后执行以上函数var t = setTimeout(printHello, 2000);// 清除定时器clearTimeout(t);

执行 main.js 文件,代码如下所示:

$ node main.js

setInterval(cb, ms)

setInterval(cb, ms) 全局函数在指定的毫秒(ms)数后执行指定函数(cb)。

返回一个代表定时器的句柄值。可以使用 clearInterval(t) 函数来清除定时器。

setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。

实例

创建文件 main.js ,代码如下所示:

function printHello(){   console.log( "Hello, World!");}// 两秒后执行以上函数setInterval(printHello, 2000);

执行 main.js 文件,代码如下所示:

$ node main.js

Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! ……

以上程序每隔两秒就会输出一次"Hello, World!",且会永久执行下去,直到你按下 ctrl + c 按钮。

console

console 用于提供控制台标准输出,它是由 Internet Explorer 的 JScript 引擎提供的调试工具,后来逐渐成为浏览器的实施标准。

Node.js 沿用了这个标准,提供与习惯行为一致的 console 对象,用于向标准输出流(stdout)或标准错误流(stderr)输出字符。

console 方法

以下为 console 对象的方法:

序号方法 & 描述
1console.log([data][, ...])
向标准输出流打印字符并以换行符结束。该方法接收若干 个参数,如果只有一个参数,则输出这个参数的字符串形式。如果有多个参数,则 以类似于C 语言 printf() 命令的格式输出。
2console.info([data][, ...])
该命令的作用是返回信息性消息,这个命令与console.log差别并不大,除了在chrome中只会输出文字外,其余的会显示一个蓝色的惊叹号。
3console.error([data][, ...])
输出错误消息的。控制台在出现错误时会显示是红色的叉子。
4console.warn([data][, ...])
输出警告消息。控制台出现有黄色的惊叹号。
5console.dir(obj[, options])
用来对一个对象进行检查(inspect),并以易于阅读和打印的格式显示。
6console.time(label)
输出时间,表示计时开始。
7console.timeEnd(label)
结束时间,表示计时结束。
8console.trace(message[, ...])
当前执行的代码在堆栈中的调用路径,这个测试函数运行很有帮助,只要给想测试的函数里面加入 console.trace 就行了。
9console.assert(value[, message][, ...])
用于判断某个表达式或变量是否为真,接收两个参数,第一个参数是表达式,第二个参数是字符串。只有当第一个参数为false,才会输出第二个参数,否则不会有任何结果。

console.log():向标准输出流打印字符并以换行符结束。

console.log 接收若干 个参数,如果只有一个参数,则输出这个参数的字符串形式。如果有多个参数,则 以类似于C 语言 printf() 命令的格式输出。

第一个参数是一个字符串,如果没有 参数,只打印一个换行。

console.log('Hello world'); console.log('byvoid%diovyb'); console.log('byvoid%diovyb', 1991); 

运行结果为:

Hello world byvoid%diovyb byvoid1991iovyb 
  • console.error():与console.log() 用法相同,只是向标准错误流输出。
  • console.trace():向标准错误流输出当前的调用栈。
console.trace();

运行结果为:

Trace: at Object.<anonymous> (/home/byvoid/consoletrace.js:1:71) 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)

实例

创建文件 main.js ,代码如下所示:

console.info("程序开始执行:");var counter = 10;console.log("计数: %d", counter);console.time("获取数据");//// 执行一些代码// console.timeEnd('获取数据');console.info("程序执行完毕。")

执行 main.js 文件,代码如下所示:

$ node main.js程序开始执行:计数: 10获取数据: 0ms程序执行完毕

process

process 是一个全局变量,即 global 对象的属性。

它用于描述当前Node.js 进程状态的对象,提供了一个与操作系统的简单接口。通常在你写本地命令行程序的时候,少不了要 和它打交道。下面将会介绍 process 对象的一些最常用的成员方法。

序号事件 & 描述
1exit
当进程准备退出时触发。
2beforeExit
当 node 清空事件循环,并且没有其他安排时触发这个事件。通常来说,当没有进程安排时 node 退出,但是 'beforeExit' 的监听器可以异步调用,这样 node 就会继续执行。
3uncaughtException
当一个异常冒泡回到事件循环,触发这个事件。如果给异常添加了监视器,默认的操作(打印堆栈跟踪信息并退出)就不会发生。
4Signal 事件
当进程接收到信号时就触发。信号列表详见标准的 POSIX 信号名,如 SIGINT、SIGUSR1 等。

实例

创建文件 main.js ,代码如下所示:

process.on('exit', function(code) {  // 以下代码永远不会执行  setTimeout(function() {    console.log("该代码不会执行");  }, 0);    console.log('退出码为:', code);});console.log("程序执行结束");

执行 main.js 文件,代码如下所示:

$ node main.js程序执行结束退出码为: 0

退出状态码

退出状态码如下所示:

状态码名称 & 描述
1Uncaught Fatal Exception
有未捕获异常,并且没有被域或 uncaughtException 处理函数处理。
2Unused
保留
3Internal JavaScript Parse Error
JavaScript的源码启动 Node 进程时引起解析错误。非常罕见,仅会在开发 Node 时才会有。
4Internal JavaScript Evaluation Failure
JavaScript 的源码启动 Node 进程,评估时返回函数失败。非常罕见,仅会在开发 Node 时才会有。
5Fatal Error
V8 里致命的不可恢复的错误。通常会打印到 stderr ,内容为: FATAL ERROR
6Non-function Internal Exception Handler
未捕获异常,内部异常处理函数不知为何设置为on-function,并且不能被调用。
7Internal Exception Handler Run-Time Failure
未捕获的异常, 并且异常处理函数处理时自己抛出了异常。例如,如果 process.on('uncaughtException') 或 domain.on('error') 抛出了异常。
8Unused
保留
9Invalid Argument
可能是给了未知的参数,或者给的参数没有值。
10Internal JavaScript Run-Time Failure
JavaScript的源码启动 Node 进程时抛出错误,非常罕见,仅会在开发 Node 时才会有。
12Invalid Debug Argument
设置了参数--debug 和/或 --debug-brk,但是选择了错误端口。
128Signal Exits
如果 Node 接收到致命信号,比如SIGKILL 或 SIGHUP,那么退出代码就是128 加信号代码。这是标准的 Unix 做法,退出信号代码放在高位。

Process 属性

Process 提供了很多有用的属性,便于我们更好的控制系统的交互:

序号.属性 & 描述
1stdout
标准输出流。
2stderr
标准错误流。
3stdin
标准输入流。
4argv
argv 属性返回一个数组,由命令行执行脚本时的各个参数组成。它的第一个成员总是node,第二个成员是脚本文件名,其余成员是脚本文件的参数。
5execPath
返回执行当前脚本的 Node 二进制文件的绝对路径。
6execArgv
返回一个数组,成员是命令行下执行脚本时,在Node可执行文件与脚本文件之间的命令行参数。
7env
返回一个对象,成员为当前 shell 的环境变量
8exitCode
进程退出时的代码,如果进程优通过 process.exit() 退出,不需要指定退出码。
9version
Node 的版本,比如v0.10.18。
10versions
一个属性,包含了 node 的版本和依赖.
11config
一个包含用来编译当前 node 执行文件的 javascript 配置选项的对象。它与运行 ./configure 脚本生成的 "config.gypi" 文件相同。
12pid
当前进程的进程号。
13title
进程名,默认值为"node",可以自定义该值。
14arch
当前 CPU 的架构:'arm'、'ia32' 或者 'x64'。
15platform
运行程序所在的平台系统 'darwin', 'freebsd', 'linux', 'sunos' 或 'win32'
16mainModule
require.main 的备选方法。不同点,如果主模块在运行时改变,require.main可能会继续返回老的模块。可以认为,这两者引用了同一个模块。

实例

创建文件 main.js ,代码如下所示:

// 输出到终端process.stdout.write("Hello World!" + "
");// 通过参数读取process.argv.forEach(function(val, index, array) {   console.log(index + ': ' + val);});// 获取执行路径console.log(process.execPath);// 平台信息console.log(process.platform);

执行 main.js 文件,代码如下所示:

$ node main.jsHello World!0: node1: /web/www/node/main.js/usr/local/node/0.10.36/bin/nodedarwin

方法参考手册

Process 提供了很多有用的方法,便于我们更好的控制系统的交互:

序号方法 & 描述
1abort()
这将导致 node 触发 abort 事件。会让 node 退出并生成一个核心文件。
2chdir(directory)
改变当前工作进程的目录,如果操作失败抛出异常。
3cwd()
返回当前进程的工作目录
4exit([code])
使用指定的 code 结束进程。如果忽略,将会使用 code 0。
5getgid()
获取进程的群组标识(参见 getgid(2))。获取到得时群组的数字 id,而不是名字。
注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。
6setgid(id)
设置进程的群组标识(参见 setgid(2))。可以接收数字 ID 或者群组名。如果指定了群组名,会阻塞等待解析为数字 ID 。
注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。
7getuid()
获取进程的用户标识(参见 getuid(2))。这是数字的用户 id,不是用户名。
注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。
8setuid(id)
设置进程的用户标识(参见setuid(2))。接收数字 ID或字符串名字。果指定了群组名,会阻塞等待解析为数字 ID 。
注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。
9getgroups()
返回进程的群组 iD 数组。POSIX 系统没有保证一定有,但是 node.js 保证有。
注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。
10setgroups(groups)
设置进程的群组 ID。这是授权操作,所以你需要有 root 权限,或者有 CAP_SETGID 能力。
注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。
11initgroups(user, extra_group)
读取 /etc/group ,并初始化群组访问列表,使用成员所在的所有群组。这是授权操作,所以你需要有 root 权限,或者有 CAP_SETGID 能力。
注意:这个函数仅在 POSIX 平台上可用(例如,非Windows 和 Android)。
12kill(pid[, signal])
发送信号给进程. pid 是进程id,并且 signal 是发送的信号的字符串描述。信号名是字符串,比如 'SIGINT' 或 'SIGHUP'。如果忽略,信号会是 'SIGTERM'。
13memoryUsage()
返回一个对象,描述了 Node 进程所用的内存状况,单位为字节。
14nextTick(callback)
一旦当前事件循环结束,调用回调函数。
15umask([mask])
设置或读取进程文件的掩码。子进程从父进程继承掩码。如果mask 参数有效,返回旧的掩码。否则,返回当前掩码。
16uptime()
返回 Node 已经运行的秒数。
17hrtime()
返回当前进程的高分辨时间,形式为 [seconds, nanoseconds]数组。它是相对于过去的任意事件。该值与日期无关,因此不受时钟漂移的影响。主要用途是可以通过精确的时间间隔,来衡量程序的性能。
你可以将之前的结果传递给当前的 process.hrtime() ,会返回两者间的时间差,用来基准和测量时间间隔。

实例

创建文件 main.js ,代码如下所示:

// 输出当前目录console.log('当前目录: ' + process.cwd());// 输出当前版本console.log('当前版本: ' + process.version);// 输出内存使用情况console.log(process.memoryUsage());

执行 main.js 文件,代码如下所示:

$ node main.js当前目录: /web/com/51coolma/nodejs当前版本: v0.10.36{ rss: 12541952, heapTotal: 4083456, heapUsed: 2157056 }

相关教程

ECMAScript教程


util 是一个Node.js 核心模块,提供常用函数的集合,用于弥补核心 JavaScript 的功能 过于精简的不足。

使用方法如下:

const util = require('util');

util.callbackify

util.callbackify(original) 将 async 异步函数(或者一个返回值为 Promise 的函数)转换成遵循异常优先的回调风格的函数,例如将 (err, value) => ... 回调作为最后一个参数。 在回调函数中,第一个参数为拒绝的原因(如果 Promise 解决,则为 null),第二个参数则是解决的值。

实例

const util = require('util');async function fn() {  return 'hello world';}const callbackFunction = util.callbackify(fn);callbackFunction((err, ret) => {  if (err) throw err;  console.log(ret);});

以上代码输出结果为:

hello world

回调函数是异步执行的,并且有异常堆栈错误追踪。 如果回调函数抛出一个异常,进程会触发一个 'uncaughtException' 异常,如果没有被捕获,进程将会退出。

null 在回调函数中作为一个参数有其特殊的意义,如果回调函数的首个参数为 Promise 拒绝的原因且带有返回值,且值可以转换成布尔值 false,这个值会被封装在 Error 对象里,可以通过属性 reason 获取。

function fn() {  return Promise.reject(null);}const callbackFunction = util.callbackify(fn);callbackFunction((err, ret) => {  // 当 Promise 被以 `null` 拒绝时,它被包装为 Error 并且原始值存储在 `reason` 中。  err && err.hasOwnProperty('reason') && err.reason === null;  // true});

original 为 async 异步函数。该函数返回传统回调函数。

util.inherits

util.inherits(constructor, superConstructor) 是一个实现对象间原型继承的函数。

JavaScript 的面向对象特性是基于原型的,与常见的基于类的不同。JavaScript 没有提供对象继承的语言级别特性,而是通过原型复制来实现的。

在这里我们只介绍 util.inherits 的用法,示例如下:

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); 

我们定义了一个基础对象 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 #&lt;Sub&gt; has no method 'sayHello' at Object.&lt;anonymous&gt; (/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) 

util.inspect

util.inspect(object,[showHidden],[depth],[colors]) 是一个将任意对象转换 为字符串的方法,通常用于调试和错误输出。它至少接受一个参数 object,即要转换的对象。

showHidden 是一个可选参数,如果值为 true,将会输出更多隐藏信息。

depth 表示最大递归的层数,如果对象很复杂,你可以指定层数以控制输出信息的多 少。如果不指定depth,默认会递归 2 层,指定为 null 表示将不限递归层数完整遍历对象。 如果 colors 值为 true,输出格式将会以 ANSI 颜色编码,通常用于在终端显示更漂亮 的效果。

特别要指出的是,util.inspect 并不会简单地直接把对象转换为字符串,即使该对 象定义了 toString 方法也不会调用。

var util = require('util'); function Person() {     this.name = 'byvoid';     this.toString = function() {     return this.name;     }; } var obj = new Person(); console.log(util.inspect(obj)); console.log(util.inspect(obj, true)); 

运行结果是:

Person { name: 'byvoid', toString: [Function] }Person {  name: 'byvoid',  toString:    { [Function]     [length]: 0,     [name]: '',     [arguments]: null,     [caller]: null,     [prototype]: { [constructor]: [Circular] } } }

util.isArray(object)

如果给定的参数 "object" 是一个数组返回 true,否则返回 false。

var util = require('util');util.isArray([])  // trueutil.isArray(new Array)  // trueutil.isArray({})  // false

util.isRegExp(object)

如果给定的参数 "object" 是一个正则表达式返回true,否则返回false。

var util = require('util');util.isRegExp(/some regexp/)  // trueutil.isRegExp(new RegExp('another regexp'))  // trueutil.isRegExp({})  // false

util.isDate(object)

如果给定的参数 "object" 是一个日期返回true,否则返回false。

var util = require('util');util.isDate(new Date())  // trueutil.isDate(Date())  // false (without 'new' returns a String)util.isDate({})  // false

更多详情可以访问 http://nodejs.org/api/util.html 了解详细内容。


Node.js 提供一组类似 UNIX(POSIX)标准的文件操作API。 Node 导入文件系统模块(fs)语法如下所示:

var fs = require("fs")

异步和同步

Node.js 文件系统(fs 模块)模块中的方法均有异步和同步版本,例如读取文件内容的函数有异步的 fs.readFile() 和同步的 fs.readFileSync()。

异步的方法函数最后一个参数为回调函数,回调函数的第一个参数包含了错误信息(error)。

建议大家使用异步方法,比起同步,异步方法性能更高,速度更快,而且没有阻塞。

实例

创建 input.txt 文件,内容如下:

编程狮官网地址:www.51coolma.cn文件读取实例

创建 file.js 文件, 代码如下:

var fs = require("fs");// 异步读取fs.readFile('input.txt', function (err, data) {   if (err) {       return console.error(err);   }   console.log("异步读取: " + data.toString());});// 同步读取var data = fs.readFileSync('input.txt');console.log("同步读取: " + data.toString());console.log("程序执行完毕。");

以上代码执行结果如下:

$ node file.js 同步读取: 编程狮官网地址:www.51coolma.cn文件读取实例程序执行完毕。异步读取: 编程狮官网地址:www.51coolma.cn文件读取实例

接下来,让我们来具体了解下 Node.js 文件系统的方法。

打开文件

语法

以下为在异步模式下打开文件的语法格式:

fs.open(path, flags[, mode], callback)

参数

参数使用说明如下:

  • path - 文件的路径。
  • flags - 文件打开的行为。具体值详见下文。
  • mode - 设置文件模式(权限),文件创建默认权限为 0666(可读,可写)。
  • callback - 回调函数,带有两个参数如:callback(err, fd)。

flags 参数可以是以下值:

Flag描述
r以读取模式打开文件。如果文件不存在抛出异常。
r+以读写模式打开文件。如果文件不存在抛出异常。
rs以同步的方式读取文件。
rs+以同步的方式读取和写入文件。
w以写入模式打开文件,如果文件不存在则创建。
wx类似 'w',但是如果文件路径存在,则文件写入失败。
w+以读写模式打开文件,如果文件不存在则创建。
wx+类似 'w+', 但是如果文件路径存在,则文件读写失败。
a以追加模式打开文件,如果文件不存在则创建。
ax类似 'a', 但是如果文件路径存在,则文件追加失败。
a+以读取追加模式打开文件,如果文件不存在则创建。
ax+类似 'a+', 但是如果文件路径存在,则文件读取追加失败。

实例

接下来我们创建 file.js 文件,并打开 input.txt 文件进行读写,代码如下所示:

var fs = require("fs");// 异步打开文件console.log("准备打开文件!");fs.open('input.txt', 'r+', function(err, fd) {   if (err) {       return console.error(err);   }  console.log("文件打开成功!");     });

以上代码执行结果如下:

$ node file.js 准备打开文件!文件打开成功!

获取文件信息

语法

以下为通过异步模式获取文件信息的语法格式:

fs.stat(path, callback)

参数

参数使用说明如下:

  • path - 文件路径。
  • callback - 回调函数,带有两个参数如:(err, stats), stats 是 fs.Stats 对象。

fs.stat(path)执行后,会将stats类的实例返回给其回调函数。可以通过stats类中的提供方法判断文件的相关属性。例如判断是否为文件:

var fs = require('fs');fs.stat('/Users/liuht/code/itbilu/demo/fs.js', function (err, stats) {    console.log(stats.isFile());         //true})

stats类中的方法有:

方法描述
stats.isFile()如果是文件返回 true,否则返回 false。
stats.isDirectory()如果是目录返回 true,否则返回 false。
stats.isBlockDevice()如果是块设备返回 true,否则返回 false。
stats.isCharacterDevice()如果是字符设备返回 true,否则返回 false。
stats.isSymbolicLink()如果是软链接返回 true,否则返回 false。
stats.isFIFO()如果是FIFO,返回true,否则返回 false。FIFO是UNIX中的一种特殊类型的命令管道。
stats.isSocket()如果是 Socket 返回 true,否则返回 false。

实例

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");console.log("准备打开文件!");fs.stat('input.txt', function (err, stats) {   if (err) {       return console.error(err);   }   console.log(stats);   console.log("读取文件信息成功!");      // 检测文件类型   console.log("是否为文件(isFile) ? " + stats.isFile());   console.log("是否为目录(isDirectory) ? " + stats.isDirectory());    });

以上代码执行结果如下:

$ node file.js 准备打开文件!{ dev: 16777220,  mode: 33188,  nlink: 1,  uid: 501,  gid: 20,  rdev: 0,  blksize: 4096,  ino: 40333161,  size: 61,  blocks: 8,  atime: Mon Sep 07 2015 17:43:55 GMT+0800 (CST),  mtime: Mon Sep 07 2015 17:22:35 GMT+0800 (CST),  ctime: Mon Sep 07 2015 17:22:35 GMT+0800 (CST) }读取文件信息成功!是否为文件(isFile) ? true是否为目录(isDirectory) ? false

写入文件

语法

以下为异步模式下写入文件的语法格式:

fs.writeFile(file, data[, options], callback)

writeFile 直接打开文件默认是 w 模式,所以如果文件存在,该方法写入的内容会覆盖旧的文件内容。

参数

参数使用说明如下:

  • file - 文件名或文件描述符。
  • data - 要写入文件的数据,可以是 String(字符串) 或 Buffer(缓冲) 对象。
  • options - 该参数是一个对象,包含 {encoding, mode, flag}。默认编码为 utf8, 模式为 0666 , flag 为 'w'
  • callback - 回调函数,回调函数只包含错误信息参数(err),在写入失败时返回。

实例

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");console.log("准备写入文件");fs.writeFile('input.txt', '我是通 过fs.writeFile 写入文件的内容',  function(err) {   if (err) {       return console.error(err);   }   console.log("数据写入成功!");   console.log("--------我是分割线-------------")   console.log("读取写入的数据!");   fs.readFile('input.txt', function (err, data) {      if (err) {         return console.error(err);      }      console.log("异步读取文件数据: " + data.toString());   });});

以上代码执行结果如下:

$ node file.js 准备写入文件数据写入成功!--------我是分割线-------------读取写入的数据!异步读取文件数据: 我是通 过fs.writeFile 写入文件的内容

读取文件

语法

以下为异步模式下读取文件的语法格式:

fs.read(fd, buffer, offset, length, position, callback)

该方法使用了文件描述符来读取文件。

参数

参数使用说明如下:

  • fd - 通过 fs.open() 方法返回的文件描述符。
  • buffer - 数据写入的缓冲区。
  • offset - 缓冲区写入的写入偏移量。
  • length - 要从文件中读取的字节数。
  • position - 文件读取的起始位置,如果 position 的值为 null,则会从当前文件指针的位置读取。
  • callback - 回调函数,有三个参数err, bytesRead, buffer,err 为错误信息, bytesRead 表示读取的字节数,buffer 为缓冲区对象。

实例

input.txt 文件内容为:

编程狮官网地址:www.51coolma.cn

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");var buf = new Buffer.alloc(1024);console.log("准备打开已存在的文件!");fs.open('input.txt', 'r+', function(err, fd) {   if (err) {       return console.error(err);   }   console.log("文件打开成功!");   console.log("准备读取文件:");   fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){      if (err){         console.log(err);      }      console.log(bytes + "  字节被读取");            // 仅输出读取的字节      if(bytes > 0){         console.log(buf.slice(0, bytes).toString());      }   });});

以上代码执行结果如下:

$ node file.js 准备打开已存在的文件!文件打开成功!准备读取文件:42  字节被读取编程狮官网地址:www.51coolma.cn

关闭文件

语法

以下为异步模式下关闭文件的语法格式:

fs.close(fd, callback)

该方法使用了文件描述符来读取文件。

参数

参数使用说明如下:

  • fd - 通过 fs.open() 方法返回的文件描述符。
  • callback - 回调函数,没有参数。

实例

input.txt 文件内容为:

编程狮官网地址:www.51coolma.cn

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");var buf = new Buffer.alloc(1024);console.log("准备打开文件!");fs.open('input.txt', 'r+', function(err, fd) {   if (err) {       return console.error(err);   }   console.log("文件打开成功!");   console.log("准备读取文件!");   fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){      if (err){         console.log(err);      }      // 仅输出读取的字节      if(bytes > 0){         console.log(buf.slice(0, bytes).toString());      }      // 关闭文件      fs.close(fd, function(err){         if (err){            console.log(err);         }          console.log("文件关闭成功");      });   });});

以上代码执行结果如下:

$ node file.js 准备打开文件!文件打开成功!准备读取文件!

编程狮官网地址:www.51coolma.cn

文件关闭成功

截取文件

语法

以下为异步模式下截取文件的语法格式:

fs.ftruncate(fd, len, callback)

该方法使用了文件描述符来读取文件。

参数

参数使用说明如下:

  • fd - 通过 fs.open() 方法返回的文件描述符。
  • len - 文件内容截取的长度。
  • callback - 回调函数,没有参数。

实例

input.txt 文件内容为:

site:www.51coolma.com

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");var buf = new Buffer.alloc(1024);console.log("准备打开文件!");fs.open('input.txt', 'r+', function(err, fd) {   if (err) {       return console.error(err);   }   console.log("文件打开成功!");   console.log("截取10字节内的文件内容,超出部分将被去除。");      // 截取文件   fs.ftruncate(fd, 10, function(err){      if (err){         console.log(err);      }       console.log("文件截取成功。");      console.log("读取相同的文件");       fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){         if (err){            console.log(err);         }         // 仅输出读取的字节         if(bytes > 0){            console.log(buf.slice(0, bytes).toString());         }         // 关闭文件         fs.close(fd, function(err){            if (err){               console.log(err);            }             console.log("文件关闭成功!");         });      });   });});

以上代码执行结果如下:

$ node file.js 准备打开文件!文件打开成功!截取10字节内的文件内容,超出部分将被去除。文件截取成功。读取相同的文件site:www.r文件关闭成功

删除文件

语法

以下为删除文件的语法格式:

fs.unlink(path, callback)

参数

参数使用说明如下:

  • path - 文件路径。
  • callback - 回调函数,没有参数。

实例

input.txt 文件内容为:

site:www.51coolma.com

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");console.log("准备删除文件!");fs.unlink('input.txt', function(err) {   if (err) {       return console.error(err);   }   console.log("文件删除成功!");});

以上代码执行结果如下:

$ node file.js 准备删除文件!文件删除成功!

再去查看 input.txt 文件,发现已经不存在了。

创建目录

语法

以下为创建目录的语法格式:

fs.mkdir(path[, options], callback)

参数

参数使用说明如下:

  • path - 文件路径。
  • options 参数可以是:recursive - 是否以递归的方式创建目录,默认为 false。mode - 设置目录权限,默认为 0777。
  • callback - 回调函数,没有参数。

实例

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");// tmp 目录必须存在console.log("创建目录 /tmp/test/");fs.mkdir("/tmp/test/",function(err){   if (err) {       return console.error(err);   }   console.log("目录创建成功。");});

以上代码执行结果如下:

$ node file.js 创建目录 /tmp/test/目录创建成功。

可以添加 recursive: true 参数,不管创建的目录 /tmp 和 /tmp/a 是否存在:

fs.mkdir('/tmp/a/apple', { recursive: true }, (err) => {  if (err) throw err;});

读取目录

语法

以下为读取目录的语法格式:

fs.readdir(path, callback)

参数

参数使用说明如下:

  • path - 文件路径。
  • callback - 回调函数,回调函数带有两个参数err, files,err 为错误信息,files 为 目录下的文件数组列表。

实例

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");console.log("查看 /tmp 目录");fs.readdir("/tmp/",function(err, files){   if (err) {       return console.error(err);   }   files.forEach( function (file){       console.log( file );   });});

以上代码执行结果如下:

$ node file.js 查看 /tmp 目录input.outoutput.outtesttest.txt

删除目录

语法

以下为删除目录的语法格式:

fs.rmdir(path, callback)

参数

参数使用说明如下:

  • path - 文件路径。
  • callback - 回调函数,没有参数。

实例

接下来我们创建 file.js 文件,代码如下所示:

var fs = require("fs");// 执行前创建一个空的 /tmp/test 目录console.log("准备删除目录 /tmp/test");fs.rmdir("/tmp/test",function(err){   if (err) {       return console.error(err);   }   console.log("读取 /tmp 目录");   fs.readdir("/tmp/",function(err, files){      if (err) {          return console.error(err);      }      files.forEach( function (file){          console.log( file );      });   });});

以上代码执行结果如下:

$ node file.js 准备删除目录 /tmp/test读取 /tmp 目录……

文件模块方法参考手册

以下为 Node.js 文件模块相同的方法列表:

序号方法 & 描述
1fs.rename(oldPath, newPath, callback)
异步 rename().回调函数没有参数,但可能抛出异常。
2fs.ftruncate(fd, len, callback)
异步 ftruncate().回调函数没有参数,但可能抛出异常。
3fs.ftruncateSync(fd, len)
同步 ftruncate()
4fs.truncate(path, len, callback)
异步 truncate().回调函数没有参数,但可能抛出异常。
5fs.truncateSync(path, len)
同步 truncate()
6fs.chown(path, uid, gid, callback)
异步 chown().回调函数没有参数,但可能抛出异常。
7fs.chownSync(path, uid, gid)
同步 chown()
8fs.fchown(fd, uid, gid, callback)
异步 fchown().回调函数没有参数,但可能抛出异常。
9fs.fchownSync(fd, uid, gid)
同步 fchown()
10fs.lchown(path, uid, gid, callback)
异步 lchown().回调函数没有参数,但可能抛出异常。
11fs.lchownSync(path, uid, gid)
同步 lchown()
12fs.chmod(path, mode, callback)
异步 chmod().回调函数没有参数,但可能抛出异常。
13fs.chmodSync(path, mode)
同步 chmod().
14fs.fchmod(fd, mode, callback)
异步 fchmod().回调函数没有参数,但可能抛出异常。
15fs.fchmodSync(fd, mode)
同步 fchmod().
16fs.lchmod(path, mode, callback)
异步 lchmod().回调函数没有参数,但可能抛出异常。Only available on Mac OS X.
17fs.lchmodSync(path, mode)
同步 lchmod().
18fs.stat(path, callback)
异步 stat(). 回调函数有两个参数 err, stats,stats 是 fs.Stats 对象。
19fs.lstat(path, callback)
异步 lstat(). 回调函数有两个参数 err, stats,stats 是 fs.Stats 对象。
20fs.fstat(fd, callback)
异步 fstat(). 回调函数有两个参数 err, stats,stats 是 fs.Stats 对象。
21fs.statSync(path)
同步 stat(). 返回 fs.Stats 的实例。
22fs.lstatSync(path)
同步 lstat(). 返回 fs.Stats 的实例。
23fs.fstatSync(fd)
同步 fstat(). 返回 fs.Stats 的实例。
24fs.link(srcpath, dstpath, callback)
异步 link().回调函数没有参数,但可能抛出异常。
25fs.linkSync(srcpath, dstpath)
同步 link().
26fs.symlink(srcpath, dstpath[, type], callback)
异步 symlink().回调函数没有参数,但可能抛出异常。 type 参数可以设置为 'dir', 'file', 或 'junction' (默认为 'file') 。
27fs.symlinkSync(srcpath, dstpath[, type])
同步 symlink().
28fs.readlink(path, callback)
异步 readlink(). 回调函数有两个参数 err, linkString。
29fs.realpath(path[, cache], callback)
异步 realpath(). 回调函数有两个参数 err, resolvedPath。
30fs.realpathSync(path[, cache])
同步 realpath()。返回绝对路径。
31fs.unlink(path, callback)
异步 unlink().回调函数没有参数,但可能抛出异常。
32fs.unlinkSync(path)
同步 unlink().
33fs.rmdir(path, callback)
异步 rmdir().回调函数没有参数,但可能抛出异常。
34fs.rmdirSync(path)
同步 rmdir().
35fs.mkdir(path[, mode], callback)
S异步 mkdir(2).回调函数没有参数,但可能抛出异常。 访问权限默认为 0777。
36fs.mkdirSync(path[, mode])
同步 mkdir().
37fs.readdir(path, callback)
异步 readdir(3). 读取目录的内容。
38fs.readdirSync(path)
同步 readdir().返回文件数组列表。
39fs.close(fd, callback)
异步 close().回调函数没有参数,但可能抛出异常。
40fs.closeSync(fd)
同步 close().
41fs.open(path, flags[, mode], callback)
异步打开文件。
42fs.openSync(path, flags[, mode])
同步 version of fs.open().
43fs.utimes(path, atime, mtime, callback)
 
44fs.utimesSync(path, atime, mtime)
修改文件时间戳,文件通过指定的文件路径。
45fs.futimes(fd, atime, mtime, callback)
 
46fs.futimesSync(fd, atime, mtime)
修改文件时间戳,通过文件描述符指定。
47fs.fsync(fd, callback)
异步 fsync.回调函数没有参数,但可能抛出异常。
48fs.fsyncSync(fd)
同步 fsync.
49fs.write(fd, buffer, offset, length[, position], callback)
将缓冲区内容写入到通过文件描述符指定的文件。
50fs.write(fd, data[, position[, encoding]], callback)
通过文件描述符 fd 写入文件内容。
51fs.writeSync(fd, buffer, offset, length[, position])
同步版的 fs.write()。
52fs.writeSync(fd, data[, position[, encoding]])
同步版的 fs.write().
53fs.read(fd, buffer, offset, length, position, callback)
通过文件描述符 fd 读取文件内容。
54fs.readSync(fd, buffer, offset, length, position)
同步版的 fs.read.
55fs.readFile(filename[, options], callback)
异步读取文件内容。
56fs.readFileSync(filename[, options])
57fs.writeFile(filename, data[, options], callback)
异步写入文件内容。
58fs.writeFileSync(filename, data[, options])
同步版的 fs.writeFile。
59fs.appendFile(filename, data[, options], callback)
异步追加文件内容。
60fs.appendFileSync(filename, data[, options])
The 同步 version of fs.appendFile.
61fs.watchFile(filename[, options], listener)
查看文件的修改。
62fs.unwatchFile(filename[, listener])
停止查看 filename 的修改。
63fs.watch(filename[, options][, listener])
查看 filename 的修改,filename 可以是文件或目录。返回 fs.FSWatcher 对象。
64fs.exists(path, callback)
检测给定的路径是否存在。
65fs.existsSync(path)
同步版的 fs.exists.
66fs.access(path[, mode], callback)
测试指定路径用户权限。
67fs.accessSync(path[, mode])
同步版的 fs.access。
68fs.createReadStream(path[, options])
返回ReadStream 对象。
69fs.createWriteStream(path[, options])
返回 WriteStream 对象。
70fs.symlink(srcpath, dstpath[, type], callback)
异步 symlink().回调函数没有参数,但可能抛出异常。

更多详情可点击查看:http://nodejs.org/api/fs.html


在很多场景中,我们的服务器都需要跟用户的浏览器打交道,如表单提交。

表单提交到服务器一般都使用 GET/POST 请求。

本章节我们将为大家介绍 Node.js GET/POST请求。

获取GET请求内容

由于GET请求直接被嵌入在路径中,URL是完整的请求路径,包括了?后面的部分,因此你可以手动解析后面的内容作为GET请求的参数。

node.js 中 url 模块中的 parse 函数提供了这个功能。

实例

var http = require('http');var url = require('url');var util = require('util'); http.createServer(function(req, res){    res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});    res.end(util.inspect(url.parse(req.url, true)));}).listen(3000);

在浏览器中访问 http://localhost:3000/user?name=编程狮&url=www.51coolma.com 然后查看返回结果:


获取 URL 的参数

我们可以使用 url.parse 方法来解析 URL 中的参数,代码如下:

实例

var http = require('http');var url = require('url');var util = require('util'); http.createServer(function(req, res){    res.writeHead(200, {'Content-Type': 'text/plain'});     // 解析 url 参数    var params = url.parse(req.url, true).query;    res.write("网站名:" + params.name);    res.write("
");    res.write("网站 URL:" + params.url);    res.end(); }).listen(3000);

在浏览器中访问 http://localhost:3000/user?name=编程狮&url=www.runoob.com 然后查看返回结果:


获取 POST 请求内容

POST 请求的内容全部的都在请求体中,http.ServerRequest 并没有一个属性内容为请求体,原因是等待请求体传输可能是一件耗时的工作。

比如上传文件,而很多时候我们可能并不需要理会请求体的内容,恶意的POST请求会大大消耗服务器的资源,所以 node.js 默认是不会解析请求体的,当你需要的时候,需要手动来做。

基本语法结构说明

var http = require('http');var querystring = require('querystring');var util = require('util'); http.createServer(function(req, res){    // 定义了一个post变量,用于暂存请求体的信息    var post = '';          // 通过req的data事件监听函数,每当接受到请求体的数据,就累加到post变量中    req.on('data', function(chunk){            post += chunk;    });     // 在end事件触发后,通过querystring.parse将post解析为真正的POST请求格式,然后向客户端返回。    req.on('end', function(){            post = querystring.parse(post);        res.end(util.inspect(post));    });}).listen(3000);

以下实例表单通过 POST 提交并输出数据:

实例

var http = require('http');var querystring = require('querystring'); var postHTML =   '<html><head><meta charset="utf-8"><title>编程狮 Node.js 实例</title></head>' +  '<body>' +  '<form method="post">' +  '网站名: <input name="name"><br>' +  '网站 URL: <input name="url"><br>' +  '<input type="submit">' +  '</form>' +  '</body></html>'; http.createServer(function (req, res) {  var body = "";  req.on('data', function (chunk) {    body += chunk;  });  req.on('end', function () {    // 解析参数    body = querystring.parse(body);    // 设置响应头部信息及编码    res.writeHead(200, {'Content-Type': 'text/html; charset=utf8'});     if(body.name && body.url) { // 输出提交的数据        res.write("网站名:" + body.name);        res.write("<br>");        res.write("网站 URL:" + body.url);    } else {  // 输出表单        res.write(postHTML);    }    res.end();  });}).listen(3000);



在 Node.js 模块库中有很多好用的模块。这些模块都是很常见的,并同时开发基于任何节点的应用程序频繁使用。接下来我们为大家介绍几种常用模块的使用:

序号模块名 & 描述
1OS 模块
提供基本的系统操作函数。
2Path 模块
提供了处理和转换文件路的工具。
3Net 模块
用于底层的网络通信。提供了服务端和客户端的的操作。
4DNS 模块
用于解析域名。
5Domain 模块
简化异步代码的异常处理,可以捕捉处理try catch无法捕捉的。

以上就是常用的Node.js工具模块,点击表格中的链接能够得到更多内容。


本节介绍Node.js Web模块,首先,你应该先了解什么是Web服务器。


什么是 Web 服务器?

Web服务器一般指网站服务器,是指驻留于因特网上某种类型计算机的程序。

Web服务器的基本功能就是提供Web信息浏览服务。它只需支持HTTP协议、HTML文档格式及URL,与客户端的网络浏览器配合。

大多数web服务器都支持服务端的脚本语言(php、python、ruby)等,并通过脚本语言从数据库获取数据,将结果返回给客户端浏览器。

目前最主流的三个Web服务器是Apache、Nginx、IIS。


Web 应用架构

  • Client - 客户端,一般指浏览器,浏览器可以通过HTTP协议向服务器请求数据。

  • Server - 服务端,一般指Web服务器,可以接收客户端请求,并向客户端发送响应数据。

  • Business - 业务层, 通过Web服务器处理应用程序,如与数据库交互,逻辑运算,调用外部程序等。

  • Data - 数据层,一般由数据库组成。


使用 Node 创建 Web 服务器

Node.js提供了http模块,http模块主要用于搭建HTTP服务端和客户端,如果要使用HTTP服务器或客户端功能,则必须调用http模块,代码如下:

var http = require('http');

以下是演示一个最基本的HTTP服务器架构(使用8081端口),创建server.js文件,代码如下所示:

var http = require('http');var fs = require('fs');var url = require('url');// 创建服务器http.createServer( function (request, response) {     // 解析请求,包括文件名   var pathname = url.parse(request.url).pathname;      // 输出请求的文件名   console.log("Request for " + pathname + " received.");      // 从文件系统中读取请求的文件内容   fs.readFile(pathname.substr(1), function (err, data) {      if (err) {         console.log(err);         // HTTP 状态码: 404 : NOT FOUND         // Content Type: text/plain         response.writeHead(404, {'Content-Type': 'text/html'});      }else{	                  // HTTP 状态码: 200 : OK         // Content Type: text/plain         response.writeHead(200, {'Content-Type': 'text/html'});	                  // 响应文件内容         response.write(data.toString());		      }      //  发送响应数据      response.end();   });   }).listen(8081);// 控制台会输出以下信息console.log('Server running at http://127.0.0.1:8081/');

接下来我们在该目录下创建一个index.html文件,代码如下:

<html><head><title>Sample Page</title></head><body>Hello World!</body></html>

执行server.js文件:

$ node server.jsServer running at http://127.0.0.1:8081/

接着我们在浏览器中输入并打开地址:http://127.0.0.1:8081/index.html,显示如下图所示:

执行server.js的控制台输出信息如下:

Server running at http://127.0.0.1:8081/Request for /index.html received.     #  客户端请求信息

Gif 实例演示

3

使用 Node 创建 Web 客户端

使用Node创建Web客户端需要引入http模块,创建client.js文件,代码如下所示:

var http = require('http');// 用于请求的选项var options = {   host: 'localhost',   port: '8081',   path: '/index.html'  };// 处理响应的回调函数var callback = function(response){   // 不断更新数据   var body = '';   response.on('data', function(data) {      body += data;   });      response.on('end', function() {      // 数据接收完成      console.log(body);   });}// 向服务端发送请求var req = http.request(options, callback);req.end();

新开一个终端,执行client.js文件,输出结果如下:

$ node client.js<html><head><title>Sample Page</title></head><body>Hello World!</body></html>

执行server.js的控制台输出信息如下:

Server running at http://127.0.0.1:8081/Request for /index.html received.   # 客户端请求信息


Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。


Express 简介

Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。

使用Express可以快速地搭建一个完整功能的网站。

Express 框架核心特性包括:

  • 可以设置中间件来响应HTTP请求。

  • 定义了路由表用于执行不同的HTTP请求动作。

  • 可以通过向模板传递参数来动态渲染HTML页面。


安装 Express

安装Express并将其保存到依赖列表中:

$ npm install express --save

以上命令会将Express框架安装在当期目录的node_modules目录中, node_modules目录下会自动创建express目录。以下几个重要的模块是需要与express框架一起安装的:

  • body-parser - node.js中间件,用于处理JSON, Raw, Text和URL编码的数据。

  • cookie-parser - 这就是一个解析Cookie的工具。通过req.cookies可以取到传过来的cookie,并把它们转成对象。

  • multer - node.js中间件,用于处理enctype="multipart/form-data"(设置表单的MIME编码)的表单数据。

$ npm install body-parser --save$ npm install cookie-parser --save$ npm install multer --save

第一个 Express 框架实例

接下来我们使用Express框架来输出"Hello World"。

以下实例中我们引入了express模块,并在客户端发起请求后,响应"Hello World"字符串。

创建express_demo.js文件,代码如下所示:

//express_demo.js 文件var express = require('express');var app = express();app.get('/', function (req, res) {   res.send('Hello World');})var server = app.listen(8081, function () {  var host = server.address().address  var port = server.address().port  console.log("应用实例,访问地址为 http://%s:%s", host, port)})

执行以上代码:

$ node express_demo.js 应用实例,访问地址为 http://0.0.0.0:8081

在浏览器中访问http://127.0.0.1:8081,结果如下图所示:


请求和响应

Express应用使用回调函数的参数: requestresponse对象来处理请求和响应的数据。

app.get('/', function (req, res) {   // --})

requestresponse对象的具体介绍:

Request 对象 - request对象表示HTTP请求,包含了请求查询字符串,参数,内容,HTTP头部等属性。常见属性有:

  1. req.app:当callback为外部文件时,用req.app访问express的实例
  2. req.baseUrl:获取路由当前安装的URL路径
  3. req.body / req.cookies:获得「请求主体」/ Cookies
  4. req.fresh / req.stale:判断请求是否还「新鲜」
  5. req.hostname / req.ip:获取主机名和IP地址
  6. req.originalUrl:获取原始请求URL
  7. req.params:获取路由的parameters
  8. req.path:获取请求路径
  9. req.protocol:获取协议类型
  10. req.query:获取URL的查询参数串
  11. req.route:获取当前匹配的路由
  12. req.subdomains:获取子域名
  13. req.accpets():检查请求的Accept头的请求类型
  14. req.acceptsCharsets / req.acceptsEncodings / req.acceptsLanguages
  15. req.get():获取指定的HTTP请求头
  16. req.is():判断请求头Content-Type的MIME类型

Response 对象 - response对象表示HTTP响应,即在接收到请求时向客户端发送的HTTP响应数据。常见属性有:

  1. res.app:同req.app一样
  2. res.append():追加指定HTTP头
  3. res.set()在res.append()后将重置之前设置的头
  4. res.cookie(name,value [,option]):设置Cookie
  5. opition: domain / expires / httpOnly / maxAge / path / secure / signed
  6. res.clearCookie():清除Cookie
  7. res.download():传送指定路径的文件
  8. res.get():返回指定的HTTP头
  9. res.json():传送JSON响应
  10. res.jsonp():传送JSONP响应
  11. res.location():只设置响应的Location HTTP头,不设置状态码或者close response
  12. res.redirect():设置响应的Location HTTP头,并且设置状态码302
  13. res.send():传送HTTP响应
  14. res.sendFile(path [,options] [,fn]):传送指定路径的文件 -会自动根据文件extension设定Content-Type
  15. res.set():设置HTTP头,传入object可以一次设置多个头
  16. res.status():设置HTTP状态码
  17. res.type():设置Content-Type的MIME类型

路由

我们已经了解了HTTP请求的基本应用,而路由决定了由谁(指定脚本)去响应客户端请求。

在HTTP请求中,我们可以通过路由提取出请求的URL以及GET/POST参数。

接下来我们扩展Hello World,添加一些功能来处理更多类型的HTTP请求。

创建express_demo2.js文件,代码如下所示:

var express = require('express');var app = express();//  主页输出 "Hello World"app.get('/', function (req, res) {   console.log("主页 GET 请求");   res.send('Hello GET');})//  POST 请求app.post('/', function (req, res) {   console.log("主页 POST 请求");   res.send('Hello POST');})//  /del_user 页面响应app.delete('/del_user', function (req, res) {   console.log("/del_user 响应 DELETE 请求");   res.send('删除页面');})//  /list_user 页面 GET 请求app.get('/list_user', function (req, res) {   console.log("/list_user GET 请求");   res.send('用户列表页面');})// 对页面 abcd, abxcd, ab123cd, 等响应 GET 请求app.get('/ab*cd', function(req, res) {      console.log("/ab*cd GET 请求");   res.send('正则匹配');})var server = app.listen(8081, function () {  var host = server.address().address  var port = server.address().port  console.log("应用实例,访问地址为 http://%s:%s", host, port)})

执行以上代码:

$ node express_demo2.js 应用实例,访问地址为 http://0.0.0.0:8081

接下来你可以尝试访问http://127.0.0.1:8081不同的地址,查看效果。

在浏览器中访问http://127.0.0.1:8081/list_user,结果如下图所示:

1

在浏览器中访问http://127.0.0.1:8081/abcd,结果如下图所示:

1

在浏览器中访问http://127.0.0.1:8081/abcdefg,结果如下图所示:

11

静态文件

Express提供了内置的中间件express.static来设置静态文件如:图片,CSS, JavaScript等。

你可以使用express.static中间件来设置静态文件路径。例如,如果你将图片, CSS, JavaScript文件放在public目录下,你可以这么写:

app.use(express.static('public'));

我们可以到public/images目录下放些图片,如下所示:

node_modulesserver.jspublic/public/imagespublic/images/logo.png

让我们再修改下"Hello Word"应用添加处理静态文件的功能。

创建express_demo3.js文件,代码如下所示:

var express = require('express');var app = express();app.use(express.static('public'));app.get('/', function (req, res) {   res.send('Hello World');})var server = app.listen(8081, function () {  var host = server.address().address  var port = server.address().port  console.log("应用实例,访问地址为 http://%s:%s", host, port)})

执行以上代码:

$ node express_demo3.js 应用实例,访问地址为 http://0.0.0.0:8081

执行以上代码:

在浏览器中访问 http://127.0.0.1:8081/images/logo.png(本实例采用了W3Cschool教程的logo),结果如下图所示:


GET 方法

以下实例演示了在表单中通过GET方法提交两个参数,我们可以使用server.js文件内的process_get路由器来处理输入:

index.htm文件代码如下:

<html><body><form action="http://127.0.0.1:8081/process_get" method="GET">First Name: <input type="text" name="first_name">  <br>Last Name: <input type="text" name="last_name"><input type="submit" value="Submit"></form></body></html>

server.js文件代码如下:

var express = require('express');var app = express();app.use(express.static('public'));app.get('/index.htm', function (req, res) {   res.sendFile( __dirname + "/" + "index.htm" );})app.get('/process_get', function (req, res) {   // 输出 JSON 格式   response = {       first_name:req.query.first_name,       last_name:req.query.last_name   };   console.log(response);   res.end(JSON.stringify(response));})var server = app.listen(8081, function () {  var host = server.address().address  var port = server.address().port  console.log("应用实例,访问地址为 http://%s:%s", host, port)})

执行以上代码:

node server.js 应用实例,访问地址为 http://0.0.0.0:8081

浏览器访问 http://127.0.0.1:8081/index.htm,如图所示:

1

现在你可以向表单输入数据,并提交,如下演示:

4

POST 方法

以下实例演示了在表单中通过POST方法提交两个参数,我们可以使用server.js文件内的process_post路由器来处理输入:

index.htm文件代码修改如下:

<html><body><form action="http://127.0.0.1:8081/process_post" method="POST">First Name: <input type="text" name="first_name">  <br>Last Name: <input type="text" name="last_name"><input type="submit" value="Submit"></form></body></html>

server.js文件代码修改如下:

var express = require('express');var app = express();var bodyParser = require('body-parser');// 创建 application/x-www-form-urlencoded 编码解析var urlencodedParser = bodyParser.urlencoded({ extended: false })app.use(express.static('public'));app.get('/index.htm', function (req, res) {   res.sendFile( __dirname + "/" + "index.htm" );})app.post('/process_post', urlencodedParser, function (req, res) {   // 输出 JSON 格式   response = {       first_name:req.body.first_name,       last_name:req.body.last_name   };   console.log(response);   res.end(JSON.stringify(response));})var server = app.listen(8081, function () {  var host = server.address().address  var port = server.address().port  console.log("应用实例,访问地址为 http://%s:%s", host, port)})

执行以上代码:

$ node server.js应用实例,访问地址为 http://0.0.0.0:8081

浏览器访问http://127.0.0.1:8081/index.htm,如图所示:

1

现在你可以向表单输入数据,并提交,如下演示:

6

文件上传

以下我们创建一个用于上传文件的表单,使用POST方法,表单enctype属性设置为multipart/form-data。

index.htm文件代码修改如下:

<html><head><title>文件上传表单</title></head><body><h3>文件上传:</h3>选择一个文件上传: <br /><form action="/file_upload" method="post" enctype="multipart/form-data"><input type="file" name="image" size="50" /><br /><input type="submit" value="上传文件" /></form></body></html>

server.js文件代码修改如下:

var express = require('express');var app = express();var fs = require("fs");var bodyParser = require('body-parser');var multer  = require('multer');app.use(express.static('public'));app.use(bodyParser.urlencoded({ extended: false }));app.use(multer({ dest: '/tmp/'}).array('image'));app.get('/index.htm', function (req, res) {   res.sendFile( __dirname + "/" + "index.htm" );})app.post('/file_upload', function (req, res) {   console.log(req.files[0]);  // 上传的文件信息   var des_file = __dirname + "/" + req.files[0].originalname;   fs.readFile( req.files[0].path, function (err, data) {        fs.writeFile(des_file, data, function (err) {         if( err ){              console.log( err );         }else{               response = {                   message:'File uploaded successfully',                    filename:req.files[0].originalname              };          }          console.log( response );          res.end( JSON.stringify( response ) );       });   });})var server = app.listen(8081, function () {  var host = server.address().address  var port = server.address().port  console.log("应用实例,访问地址为 http://%s:%s", host, port)})

执行以上代码:

$ node server.js 应用实例,访问地址为 http://0.0.0.0:8081

浏览器访问http://127.0.0.1:8081/index.htm,如图所示:

1111

现在你可以向表单输入数据,并提交,如下演示:

1

Cookie 管理

我们可以使用中间件向Node.js服务器发送cookie信息,以下代码输出了客户端发送的cookie信息:

// express_cookie.js 文件var express      = require('express')var cookieParser = require('cookie-parser')var app = express()app.use(cookieParser())app.get('/', function(req, res) {  console.log("Cookies: ", req.cookies)})app.listen(8081)

执行以上代码:

$ node express_cookie.js 

现在你可以访问 http://127.0.0.1:8081 并查看终端信息的输出,如下演示:

3


本节介绍Node.js的RESTful API。

什么是 REST?

REST中文解释为,表述性状态传递(英文:Representational State Transfer,简称REST),是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。

表述性状态转移是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是RESTful。

需要注意的是,REST是设计风格而不是标准。REST通常基于使用HTTP,URI和XML(标准通用标记语言下的一个子集)以及HTML(标准通用标记语言下的一个应用)这些现有的广泛流行的协议和标准。REST通常使用JSON数据格式。

HTTP 方法

以下为REST基本架构的四个方法:
  • GET - 用于获取数据。

  • PUT - 用于添加数据。

  • DELETE - 用于删除数据。

  • POST - 用于更新或添加数据。


RESTful Web Services

Web service是一个平台独立的,低耦合的,自包含的、基于可编程的web的应用程序,可使用开放的XML(标准通用标记语言下的一个子集)标准来描述、发布、发现、协调和配置这些应用程序,用于开发分布式的互操作的应用程序。

RESTful是基于REST架构的Web Services。

由于轻量级以及通过HTTP直接传输数据的特性,Web服务的RESTful方法已经成为最常见的替代方法。可以使用各种语言(比如,Java程序、Perl、Ruby、Python、PHP和Javascript[包括Ajax])实现客户端。

RESTful Web服务通常可以通过自动客户端或代表用户的应用程序访问。但是,这种服务的简便性让用户能够与之直接交互,使用它们的Web浏览器构建一个GET URL并读取返回的内容。

更多介绍,可以查看:RESTful 架构详解


创建 RESTful

首先,创建一个json数据资源文件users.json,内容如下:

{   "user1" : {      "name" : "mahesh",	  "password" : "password1",	  "profession" : "teacher",	  "id": 1   },   "user2" : {      "name" : "suresh",	  "password" : "password2",	  "profession" : "librarian",	  "id": 2   },   "user3" : {      "name" : "ramesh",	  "password" : "password3",	  "profession" : "clerk",	  "id": 3   }}

基于以上数据,我们创建以下RESTful API:

序号URIHTTP 方法发送内容结果
1listUsersGET显示所有用户列表
2addUserPOSTJSON 字符串添加新用户
3deleteUserDELETEJSON 字符串删除用户
4:idGET显示用户详细信息

获取用户列表:

以下代码,我们创建了RESTful API listUsers,用于读取用户的信息列表, server.js文件代码如下所示:

var express = require('express');var app = express();var fs = require("fs");app.get('/listUsers', function (req, res) {   fs.readFile( __dirname + "/" + "users.json", 'utf8', function (err, data) {       console.log( data );       res.end( data );   });})var server = app.listen(8081, function () {  var host = server.address().address  var port = server.address().port  console.log("应用实例,访问地址为 http://%s:%s", host, port)})

接下来执行以下命令:

$ node server.js 应用实例,访问地址为 http://0.0.0.0:8081

在浏览器中访问http://127.0.0.1:8081/listUsers,结果如下所示:

{   "user1" : {      "name" : "mahesh",      "password" : "password1",      "profession" : "teacher",      "id": 1   },   "user2" : {      "name" : "suresh",      "password" : "password2",      "profession" : "librarian",      "id": 2   },   "user3" : {      "name" : "ramesh",      "password" : "password3",      "profession" : "clerk",      "id": 3   }}

添加用户

如果要添加新的用户数据,可以通过创建RESTful API addUser实现,server.js文件代码如下所示:

var express = require('express');var app = express();var fs = require("fs");//添加的新用户数据var user = {   "user4" : {      "name" : "mohit",      "password" : "password4",      "profession" : "teacher",      "id": 4   }}app.get('/addUser', function (req, res) {   // 读取已存在的数据   fs.readFile( __dirname + "/" + "users.json", 'utf8', function (err, data) {       data = JSON.parse( data );       data["user4"] = user["user4"];       console.log( data );       res.end( JSON.stringify(data));   });})var server = app.listen(8081, function () {  var host = server.address().address  var port = server.address().port  console.log("应用实例,访问地址为 http://%s:%s", host, port)})

接下来执行以下命令:

$ node server.js 应用实例,访问地址为 http://0.0.0.0:8081

在浏览器中访问http://127.0.0.1:8081/addUser,结果如下所示:

{ user1:   { name: 'mahesh',     password: 'password1',     profession: 'teacher',     id: 1 },  user2:   { name: 'suresh',     password: 'password2',     profession: 'librarian',     id: 2 },  user3:   { name: 'ramesh',     password: 'password3',     profession: 'clerk',     id: 3 },  user4:   { name: 'mohit',     password: 'password4',     profession: 'teacher',     id: 4 } }

显示用户详情

以下代码,我们创建了RESTful API :id(用户id), 用于读取指定用户的详细信息,server.js文件代码如下所示:

var express = require('express');var app = express();var fs = require("fs");app.get('/:id', function (req, res) {   // 首先我们读取已存在的用户   fs.readFile( __dirname + "/" + "users.json", 'utf8', function (err, data) {       data = JSON.parse( data );       var user = data["user" + req.params.id]        console.log( user );       res.end( JSON.stringify(user));   });})var server = app.listen(8081, function () {  var host = server.address().address  var port = server.address().port  console.log("应用实例,访问地址为 http://%s:%s", host, port)})

接下来执行以下命令:

$ node server.js 应用实例,访问地址为 http://0.0.0.0:8081

在浏览器中访问http://127.0.0.1:8081/2,结果如下所示:

{   "name":"suresh",   "password":"password2",   "profession":"librarian",   "id":2}

删除用户

以下代码,我们创建了RESTful API deleteUser, 用于删除指定用户的详细信息,以下实例中,用户id为2,server.js文件代码如下所示:

var express = require('express');var app = express();var fs = require("fs");var id = 2;app.get('/deleteUser', function (req, res) {   // First read existing users.   fs.readFile( __dirname + "/" + "users.json", 'utf8', function (err, data) {       data = JSON.parse( data );       delete data["user" + 2];              console.log( data );       res.end( JSON.stringify(data));   });})var server = app.listen(8081, function () {  var host = server.address().address  var port = server.address().port  console.log("应用实例,访问地址为 http://%s:%s", host, port)})

接下来执行以下命令:

$ node server.js 应用实例,访问地址为 http://0.0.0.0:8081

在浏览器中访问http://127.0.0.1:8081/deleteUser,结果如下所示:

{ user1:   { name: 'mahesh',     password: 'password1',     profession: 'teacher',     id: 1 },  user3:   { name: 'ramesh',     password: 'password3',     profession: 'clerk',     id: 3 } }


Node.js本身是以单线程的模式运行的,但它使用的是事件驱动来处理并发,这样有助于我们在多核 cpu 的系统上创建多个子进程,从而提高性能。

每个子进程总是带有三个流对象:child.stdin, child.stdout和child.stderr。他们可能会共享父进程的stdio流,或者也可以是独立的被导流的流对象。

Node提供了child_process模块来创建子进程,方法有:

  • exec - child_process.exec使用子进程执行命令,缓存子进程的输出,并将子进程的输出以回调函数参数的形式返回。

  • spawn - child_process.spawn使用指定的命令行参数创建新进程。

  • fork - child_process.fork是spawn()的特殊形式,用于在子进程中运行的模块,如fork('./son.js')相当于spawn('node', ['./son.js']) 。与spawn方法不同的是,fork会在父进程与子进程之间,建立一个通信管道,用于进程之间的通信。


exec() 方法

child_process.exec使用子进程执行命令,缓存子进程的输出,并将子进程的输出以回调函数参数的形式返回。

语法如下所示:

child_process.exec(command[, options], callback)

参数

参数说明如下:

command: 字符串, 将要运行的命令,参数使用空格隔开

options :对象,可以是:

  • cwd,字符串,子进程的当前工作目录
  • env,对象,环境变量键值对
  • encoding,字符串,字符编码(默认: 'utf8')
  • shell,字符串,将要执行命令的Shell(默认: 在UNIX中为/bin/sh, 在Windows中为cmd.exe, Shell应当能识别-c开关在UNIX中,或/s /c在 Windows中。 在Windows中,命令行解析应当能兼容cmd.exe
  • timeout,数字,超时时间(默认: 0)
  • maxBuffer,数字, 在stdout或stderr中允许存在的最大缓冲(二进制),如果超出那么子进程将会被杀死(默认: 200*1024)
  • killSignal,字符串,结束信号(默认:'SIGTERM')
  • uid,数字,设置用户进程的ID
  • gid,数字,设置进程组的ID

callback :回调函数,包含三个参数error, stdout和stderr。

exec()方法返回最大的缓冲区,并等待进程结束,一次性返回缓冲区的内容。

实例

让我们创建两个js文件support.js和master.js。

support.js文件代码:

console.log("进程 " + process.argv[2] + " 执行。" );

master.js文件代码:

const fs = require('fs');const child_process = require('child_process');for(var i=0; i<3; i++) {   var workerProcess = child_process.exec('node support.js '+i,      function (error, stdout, stderr) {         if (error) {            console.log(error.stack);            console.log('Error code: '+error.code);            console.log('Signal received: '+error.signal);         }         console.log('stdout: ' + stdout);         console.log('stderr: ' + stderr);      });      workerProcess.on('exit', function (code) {      console.log('子进程已退出,退出码 '+code);   });}

执行以上代码,输出结果为:

$ node master.js 子进程已退出,退出码 0stdout: 进程 1 执行。stderr: 子进程已退出,退出码 0stdout: 进程 0 执行。stderr: 子进程已退出,退出码 0stdout: 进程 2 执行。stderr: 

spawn() 方法

child_process.spawn使用指定的命令行参数创建新进程,语法格式如下:

child_process.spawn(command[, args][, options])

参数

参数说明如下:

command: 将要运行的命令

args: Array字符串参数数组

options Object

  • cwd:String,子进程的当前工作目录
  • env:Object,环境变量键值对
  • stdio:Array|String,子进程的stdio配置
  • detached:Boolean,这个子进程将会变成进程组的领导
  • uid:Number,设置用户进程的ID
  • gid:Number,设置进程组的ID

spawn()方法返回流 (stdout & stderr),在进程返回大量数据时使用。进程开始执行spawn()时就开始接收响应。

实例

在这个实例中我们创建两个js文件support.js和master.js。

support.js文件代码:

console.log("进程 " + process.argv[2] + " 执行。" );

master.js文件代码:

const fs = require('fs');const child_process = require('child_process'); for(var i=0; i<3; i++) {   var workerProcess = child_process.spawn('node', ['support.js', i]);   workerProcess.stdout.on('data', function (data) {      console.log('stdout: ' + data);   });   workerProcess.stderr.on('data', function (data) {      console.log('stderr: ' + data);   });   workerProcess.on('close', function (code) {      console.log('子进程已退出,退出码 '+code);   });}

执行以上代码,输出结果为:

$ node master.js stdout: 进程 0 执行。子进程已退出,退出码 0stdout: 进程 1 执行。子进程已退出,退出码 0stdout: 进程 2 执行。子进程已退出,退出码 0

fork 方法

child_process.fork是spawn()方法的特殊形式,用于创建进程,语法格式如下:

child_process.fork(modulePath[, args][, options])

参数

参数说明如下:

modulePath: String,将要在子进程中运行的模块

args: Array,字符串参数数组

options:Object

  • cwd:String,子进程的当前工作目录
  • env:Object,环境变量键值对
  • execPath:String,创建子进程的可执行文件
  • execArgv:Array,子进程的可执行文件的字符串参数数组(默认: process.execArgv)
  • silent:Boolean,如果为true,子进程的stdinstdoutstderr将会被关联至父进程,否则,它们将会从父进程中继承。(默认为:false
  • uid:Number,设置用户进程的ID
  • gid:Number,设置进程组的ID

返回的对象除了拥有ChildProcess实例的所有方法,还有一个内建的通信信道。

实例

让我们创建两个js文件support.js和master.js。

support.js文件代码如下所示:

console.log("进程 " + process.argv[2] + " 执行。" );

master.js文件代码如下所示:

const fs = require('fs');const child_process = require('child_process'); for(var i=0; i<3; i++) {   var worker_process = child_process.fork("support.js", [i]);	   worker_process.on('close', function (code) {      console.log('子进程已退出,退出码 ' + code);   });}

执行以上代码,输出结果为:

$ node master.js 进程 0 执行。子进程已退出,退出码 0进程 1 执行。子进程已退出,退出码 0进程 2 执行。子进程已退出,退出码 0


Node.js是一个开放源代码、跨平台的、用于服务器端和网络应用的运行环境。

JXcore是一个支持多线程的 Node.js 发行版本,基本不需要对你现有的代码做任何改动就可以直接线程安全地以多线程运行。

本文主要介绍JXcore的打包功能。


JXcore 安装

下载JXcore安装包,然后进行解压,在解压的目录下提供了jx二进制文件命令,接下来我们主要使用这个命令。

步骤1、下载

在 https://github.com/jxcore/jxcore-release 中下载JXcore安装包,你需要根据你自己的系统环境来下载安装包:

1、Window系统下载:Download

2、Linux/OSX下载安装命令,直接下载解压包下的jx二进制文件,然后拷贝到/usr/bin目录下:

$ wget https://s3.amazonaws.com/nodejx/jx_rh64.zip$ unzip jx_rh64.zip$ cp jx_rh64/jx /usr/bin

将/usr/bin添加到PATH路径中:

$ export PATH=$PATH:/usr/bin

以上步骤如果操作正确,使用以下命令,会输出版本号信息:

$ jx --versionv0.10.32

包代码

例如,我们的Node.js项目包含以下几个文件,其中index.js是主文件:

drwxr-xr-x  2 root root  4096 Nov 13 12:42 images-rwxr-xr-x  1 root root 30457 Mar  6 12:19 index.htm-rwxr-xr-x  1 root root 30452 Mar  1 12:54 index.jsdrwxr-xr-x 23 root root  4096 Jan 15 03:48 node_modulesdrwxr-xr-x  2 root root  4096 Mar 21 06:10 scriptsdrwxr-xr-x  2 root root  4096 Feb 15 11:56 style

接下来我们使用jx命令打包以上项目,并指定index.js为Node.js项目的主文件:

$ jx package index.js index

以上命令执行成功,会生成以下两个文件:

  • index.jxp:这是一个中间件文件,包含了需要编译的完整项目信息。

  • index.jx:这是一个完整包信息的二进制文件,可运行在客户端上。


载入 JX 文件

我们使用jx命令打包项目:

$ node index.js command_line_arguments

使用JXcore编译后,我们可以使用以下命令来执行生成的jx二进制文件:

$ jx index.jx command_line_arguments

更多JXcore功能特性你可以参考官网:http://jxcore.com/


本章节我们将为大家介绍如何使用 Node.js 来连接 MySQL,并对数据库进行操作。

如果你还没有 MySQL 的基本知识,可以参考我们的教程:MySQL 教程

本教程使用到的 Websites 表 SQL 文件:websites.sql

安装驱动

本教程使用了淘宝定制的 cnpm 命令进行安装:

$ cnpm install mysql

连接数据库

在以下实例中根据你的实际配置修改数据库用户名、及密码及数据库名:

test.js 文件代码:

var mysql      = require('mysql');var connection = mysql.createConnection({  host     : 'localhost',  user     : 'root',  password : '123456',  database : 'test'}); connection.connect(); connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) {  if (error) throw error;  console.log('The solution is: ', results[0].solution);});

执行以下命令输出结果为:

$ node test.jsThe solution is: 2

数据库连接参数说明:

参数描述
host主机地址 (默认:localhost)
  user用户名
  password密码
  port端口号 (默认:3306)
  database数据库名
  charset连接字符集(默认:'UTF8_GENERAL_CI',注意字符集的字母都要大写)
  localAddress此IP用于TCP连接(可选)
  socketPath连接到unix域路径,当使用 host 和 port 时会被忽略
  timezone时区(默认:'local')
  connectTimeout连接超时(默认:不限制;单位:毫秒)
  stringifyObjects是否序列化对象
  typeCast是否将列值转化为本地JavaScript类型值 (默认:true)
  queryFormat自定义query语句格式化方法
  supportBigNumbers数据库支持bigint或decimal类型列时,需要设此option为true (默认:false)
  bigNumberStringssupportBigNumbers和bigNumberStrings启用 强制bigint或decimal列以JavaScript字符串类型返回(默认:false)
  dateStrings强制timestamp,datetime,data类型以字符串类型返回,而不是JavaScript Date类型(默认:false)
  debug开启调试(默认:false)
  multipleStatements是否许一个query中有多个MySQL语句 (默认:false)
  flags用于修改连接标志
  ssl使用ssl参数(与crypto.createCredenitals参数格式一至)或一个包含ssl配置文件名称的字符串,目前只捆绑Amazon RDS的配置文件

更多说明可参见:https://github.com/mysqljs/mysql

数据库操作( CURD )

在进行数据库操作前,你需要将本站提供的 Websites 表 SQL 文件websites.sql 导入到你的 MySQL 数据库中。

本教程测试的 MySQL 用户名为 root,密码为 123456,数据库为 test,你需要根据自己配置情况修改。

查询数据

将上面我们提供的 SQL 文件导入数据库后,执行以下代码即可查询出数据:

查询数据

var mysql  = require('mysql');   var connection = mysql.createConnection({       host     : 'localhost',         user     : 'root',                password : '123456',         port: '3306',                     database: 'test' });  connection.connect(); var  sql = 'SELECT * FROM websites';//查connection.query(sql,function (err, result) {        if(err){          console.log('[SELECT ERROR] - ',err.message);          return;        }        console.log('--------------------------SELECT----------------------------');       console.log(result);       console.log('------------------------------------------------------------

');  }); connection.end();

执行以下命令输出就结果为:

$ node test.js--------------------------SELECT----------------------------[ RowDataPacket {    id: 1,    name: 'Google',    url: 'https://www.google.cm/',    alexa: 1,    country: 'USA' },  RowDataPacket {    id: 2,    name: '淘宝',    url: 'https://www.taobao.com/',    alexa: 13,    country: 'CN' },  RowDataPacket {    id: 3,    name: '编程狮',    url: 'http://www.51coolma.cn/',
alexa: 4689, country: 'CN' }, RowDataPacket { id: 4, name: '微博', url: 'http://weibo.com/', alexa: 20, country: 'CN' }, RowDataPacket { id: 5, name: 'Facebook', url: 'https://www.facebook.com/', alexa: 3, country: 'USA' } ]------------------------------------------------------------

插入数据

我们可以向数据表 websties 插入数据:

插入数据

var mysql  = require('mysql');   var connection = mysql.createConnection({       host     : 'localhost',         user     : 'root',                password : '123456',         port: '3306',                     database: 'test' });  connection.connect(); var  addSql = 'INSERT INTO websites(Id,name,url,alexa,country) VALUES(0,?,?,?,?)';var  addSqlParams = ['编程狮在线工具, 'https://123.51coolma.cn/webtools','23453', 'CN'];
//增connection.query(addSql,addSqlParams,function (err, result) { if(err){ console.log('[INSERT ERROR] - ',err.message); return; } console.log('--------------------------INSERT----------------------------'); //console.log('INSERT ID:',result.insertId); console.log('INSERT ID:',result); console.log('----------------------------------------------------------------- '); }); connection.end();

执行以下命令输出就结果为:

$ node test.js--------------------------INSERT----------------------------INSERT ID: OkPacket {  fieldCount: 0,  affectedRows: 1,  insertId: 6,  serverStatus: 2,  warningCount: 0,  message: '',  protocol41: true,  changedRows: 0 }-----------------------------------------------------------------

更新数据

我们也可以对数据库的数据进行修改:

更新数据

var mysql  = require('mysql');  var connection = mysql.createConnection({       host     : 'localhost',         user     : 'root',                password : '123456',         port: '3306',                     database: 'test' });  connection.connect(); var modSql = 'UPDATE websites SET name = ?,url = ? WHERE Id = ?';var modSqlParams = ['编程狮', 'https://m.51coolma.cn',6];//改connection.query(modSql,modSqlParams,function (err, result) {   if(err){         console.log('[UPDATE ERROR] - ',err.message);         return;   }          console.log('--------------------------UPDATE----------------------------');  console.log('UPDATE affectedRows',result.affectedRows);  console.log('-----------------------------------------------------------------

');}); connection.end();

执行以下命令输出就结果为:

--------------------------UPDATE----------------------------UPDATE affectedRows 1-----------------------------------------------------------------

删除数据

我们可以使用以下代码来删除 id 为 6 的数据:

删除数据

var mysql  = require('mysql');   var connection = mysql.createConnection({       host     : 'localhost',         user     : 'root',                password : '123456',         port: '3306',                     database: 'test' });  connection.connect(); var delSql = 'DELETE FROM websites where id=6';//删connection.query(delSql,function (err, result) {        if(err){          console.log('[DELETE ERROR] - ',err.message);          return;        }                console.log('--------------------------DELETE----------------------------');       console.log('DELETE affectedRows',result.affectedRows);       console.log('-----------------------------------------------------------------

');  }); connection.end();

执行以下命令输出就结果为:

--------------------------DELETE----------------------------DELETE affectedRows 1-----------------------------------------------------------------





MongoDB是一种文档导向数据库管理系统,由C++撰写而成。

本章节我们将为大家介绍如何使用 Node.js 来连接 MongoDB,并对数据库进行操作。

如果你还没有 MongoDB 的基本知识,可以参考我们的教程:MongoDB 教程

安装驱动

本教程使用了淘宝定制的 cnpm 命令进行安装:

$ cnpm install mongodb

接下来我们来实现增删改查功能。

创建数据库

要在 MongoDB 中创建一个数据库,首先我们需要创建一个 MongoClient 对象,然后配置好指定的 URL 和 端口号。

如果数据库不存在,MongoDB 将创建数据库并建立连接。

创建连接

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/51coolma"; 
MongoClient.connect(url, function(err, db) { if (err) throw err; console.log("数据库已创建!"); db.close(); });

创建集合

我们可以使用 createCollection() 方法来创建集合:

创建集合

var MongoClient = require('mongodb').MongoClient; var url = 'mongodb://localhost:27017/51coolma'; 
MongoClient.connect(url, function (err, db) { if (err) throw err; console.log('数据库已创建'); var dbase = db.db("51coolma"); dbase.createCollection('site', function (err, res) { if (err) throw err; console.log("创建集合!"); db.close(); }); });

数据库操作( CURD )

与 MySQL 不同的是 MongoDB 会自动创建数据库和集合,所以使用前我们不需要手动去创建。

插入数据

以下实例我们连接数据库 51coolma的 site 表,并插入一条数据条数据,使用 insertOne():

插入一条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma"); var myobj = { name: "编程狮", url: "www.51coolma" };
dbo.collection("site").insertOne(myobj, function(err, res) { if (err) throw err; console.log("文档插入成功"); db.close(); }); });

执行以下命令输出就结果为:

$ node test.js文档插入成功

从输出结果来看,数据已插入成功。

我们也可以打开 MongoDB 的客户端查看数据,如:

> show dbsrunoob  0.000GB          # 自动创建了 51coolma数据库
> show tablessite # 自动创建了 site 集合(数据表)> db.site.find(){ "_id" : ObjectId("5a794e36763eb821b24db854"), "name" : "编程狮", "url" : "www.51coolma" }
>

如果要插入多条数据可以使用 insertMany():

插入多条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma");
var myobj = [ { name: '编程狮工具', url: 'https://c.51coolma.com', type: 'cn'},
{ name: 'Google', url: 'https://www.google.com', type: 'en'}, { name: 'Facebook', url: 'https://www.google.com', type: 'en'} ]; dbo.collection("site").insertMany(myobj, function(err, res) { if (err) throw err; console.log("插入的文档数量为: " + res.insertedCount); db.close(); }); });

res.insertedCount 为插入的条数。

查询数据

可以使用 find() 来查找数据, find() 可以返回匹配条件的所有数据。 如果未指定条件,find() 返回集合中的所有数据。

find()

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma"); 
dbo.collection("site"). find({}).toArray(function(err, result) { // 返回集合中所有数据 if (err) throw err; console.log(result); db.close(); }); });

以下实例检索 name 为 "编程狮" 的实例:

查询指定条件的数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma"); 
var whereStr = {"name":'编程狮'}; // 查询条件 dbo.collection("site").find(whereStr).toArray(function(err, result) { if (err) throw err; console.log(result); db.close(); }); });

执行以下命令输出就结果为:

[ { _id: 5a794e36763eb821b24db854,    name: '编程狮',    url: 'www.51coolma' } ]

更新数据

我们也可以对数据库的数据进行修改,以下实例将 name 为 "菜鸟教程" 的 url 改为 https://www.runoob.com:

更新一条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma");
var whereStr = {"name":'编程狮'}; // 查询条件 var updateStr = {$set: { "url" : "https://www.51coolma.cn" }};
dbo.collection("site").updateOne(whereStr, updateStr, function(err, res) { if (err) throw err; console.log("文档更新成功"); db.close(); }); });

执行成功后,进入 mongo 管理工具查看数据已修改:

> db.site.find().pretty(){    "_id" : ObjectId("5a794e36763eb821b24db854"),    "name" : "编程狮",    "url" : "https://www.51coolma.cn"     // 已修改为 https
}

如果要更新所有符合条的文档数据可以使用 updateMany():

更新多条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma"); 
var whereStr = {"type":'en'}; // 查询条件 var updateStr = {$set: { "url" : "https://www.51coolma.cn" }};
dbo.collection("site").updateMany(whereStr, updateStr, function(err, res) { if (err) throw err; console.log(res.result.nModified + " 条文档被更新"); db.close(); }); });

result.nModified 为更新的条数。

删除数据

以下实例将 name 为 "编程狮" 的数据删除 :

删除一条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma"); 
var whereStr = {"name":'编程狮'}; // 查询条件 dbo.collection("site").deleteOne(whereStr, function(err, obj) { if (err) throw err; console.log("文档删除成功"); db.close(); }); });

执行成功后,进入 mongo 管理工具查看数据已删除:

> db.site.find()> 

如果要删除多条语句可以使用 deleteMany() 方法

以下实例将 type 为 en 的所有数据删除 :

删除多条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma"); 
var whereStr = { type: "en" }; // 查询条件 dbo.collection("site").deleteMany(whereStr, function(err, obj) { if (err) throw err; console.log(obj.result.n + " 条文档被删除"); db.close(); }); });

obj.result.n 删除的条数。

排序

排序 使用 sort() 方法,该方法接受一个参数,规定是升序(1)还是降序(-1)。

例如:

{ type: 1 }  // 按 type 字段升序{ type: -1 } // 按 type 字段降序

按 type 升序排列:

排序

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma"); 
var mysort = { type: 1 }; dbo.collection("site").find().sort(mysort).toArray(function(err, result) { if (err) throw err; console.log(result); db.close(); }); });

查询分页

如果要设置指定的返回条数可以使用 limit() 方法,该方法只接受一个参数,指定了返回的条数。

limit():读取两条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma"); 
dbo.collection("site").find().limit(2).toArray(function(err, result) { if (err) throw err; console.log(result); db.close(); }); });

如果要指定跳过的条数,可以使用 skip() 方法。

skip(): 跳过前面两条数据,读取两条数据

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma");
dbo.collection("site").find().skip(2).limit(2).toArray(function(err, result) { if (err) throw err; console.log(result); db.close(); }); });

连接操作

mongoDB 不是一个关系型数据库,但我们可以使用 $lookup 来实现左连接。

例如我们有两个集合数据分别为:

集合1:orders

[  { _id: 1, product_id: 154, status: 1 }]

集合2:products

[  { _id: 154, name: '笔记本电脑' },  { _id: 155, name: '耳机' },  { _id: 156, name: '台式电脑' }]

$lookup 实现左连接

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://127.0.0.1:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma");
dbo.collection('orders').aggregate([ { $lookup: { from: 'products', // 右集合 localField: 'product_id', // 左集合 join 字段 foreignField: '_id', // 右集合 join 字段 as: 'orderdetails' // 新生成字段(类型array) } } ]).toArray(function(err, res) { if (err) throw err; console.log(JSON.stringify(res)); db.close(); }); });

删除集合

我们可以使用 drop() 方法来删除集合:

drop()

var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; var dbo = db.db("51coolma"); 
// 删除 test 集合 dbo.collection("test").drop(function(err, delOK) { // 执行成功 delOK 返回 true,否则返回 false if (err) throw err; if (delOK) console.log("集合已删除"); db.close(); }); });

使用 Promise

Promise 是一个 ECMAScript 6 提供的类,目的是更加优雅地书写复杂的异步任务。

如果你还不了解 Promise,可以参考 JavaScript Promise

以下实例使用 Promise 创建集合:

实例

const MongoClient = require("mongodb").MongoClient; const url = "mongodb://localhost/51coolma";
MongoClient.connect(url).then((conn) => { console.log("数据库已连接"); var dbase = conn.db("51coolma");
dbase.createCollection("site").then((res) => { console.log("已创建集合"); }).catch((err) => { console.log("数据库操作错误"); }).finally(() => { conn.close(); }); }).catch((err) => { console.log("数据库连接失败"); });

Promise 数据操作

现在我们在一个程序中实现四个连续操作:增加 、查询 、更改 、删除。

实例

const MongoClient = require("mongodb").MongoClient; const url = "mongodb://localhost/"; MongoClient.connect(url).then((conn) => { console.log("数据库已连接"); const test = conn.db("testdb").collection("test"); // 增加 test.insertOne({ "site": "51coolma.cn" }).then((res) => { 
// 查询 return test.find().toArray().then((arr) => { console.log(arr); }); }).then(() => { // 更改 return test.updateMany({ "site": "51coolma.cn" }, { $set: { "site": "example.com" } });
}).then((res) => { // 查询 return test.find().toArray().then((arr) => { console.log(arr); }); }).then(() => { // 删除 return test.deleteMany({ "site": "example.com" }); }).then((res) => { // 查询 return test.find().toArray().then((arr) => { console.log(arr); }); }).catch((err) => { console.log("数据操作失败" + err.message); }).finally(() => { conn.close(); }); }).catch((err) => { console.log("数据库连接失败"); });

执行结果:

数据库已连接[ { _id: 5f1664966833e531d83d3ac6, site: '51coolma.cn' } ]
[ { _id: 5f1664966833e531d83d3ac6, site: 'example.com' } ][]

用异步函数实现相同的数据操作

实例

const MongoClient = require("mongodb").MongoClient; const url = "mongodb://localhost/"; async function dataOperate() { var conn = null; try { conn = await MongoClient.connect(url); console.log("数据库已连接"); const test = conn.db("testdb").collection("test"); // 增加 await test.insertOne({ "site": "51coolma.cn" }); 
// 查询 var arr = await test.find().toArray(); console.log(arr); // 更改 await test.updateMany({ "site": "51coolma.cn" }, { $set: { "site": "example.com" } }); // 查询 arr = await test.find().toArray(); console.log(arr); // 删除 await test.deleteMany({ "site": "example.com" }); // 查询 arr = await test.find().toArray(); console.log(arr); } catch (err) { console.log("错误:" + err.message); } finally { if (conn != null) conn.close(); } } dataOperate();

运行结果:

数据库已连接[ { _id: 5f169006a2780f0cd4ea640b, site: '51coolma.cn' } ][ { _id: 5f169006a2780f0cd4ea640b, site: 'example.com' } ][]

运行结果完全一样。

很显然,异步函数是一种非常良好的编程风格,在多次使用异步操作的时候非常实用。

但是请勿在低于 7.6.0 版本的 node.js 上使用异步函数。




本文档翻译自 Node.js 官方文档,适用于 V0.12.2。

本文档将从引用参考和概念两个方面来对Node.js API进行全面的解释,让你更加了解Node.js API。

Node.js官方文档的每个章节描述了一个内置模块或高级概念。

一般情况下,属性、方法参数,以及提供给事件处理程序的参数都会在主标题下的列表中详细说明。

每个.html文档都有对应的.json,它们包含相同的结构化内容。这些东西目前还是实验性的,主要为各种集成开发环境(IDE)和开发工具提供便利。

每个.html.json文件都和doc/api/目录下的.markdown文件相对应。这些文档使用tools/doc/generate.js程序生成。 HTML模板位于doc/template.html

稳定性标志

在文档中,你会看到每个章节的稳定性标志。Node.js API还在改进中,成熟的部分会比其他章节值得信赖。经过大量验证和依赖的API是一般是不会变的。其他新增的,试验性的,或被证明具有危险性的部分正被重新设计。

稳定性标志包括以下内容:

稳定性(Stability): 0 - 抛弃这部分内容有问题,并已计划改变。不要使用这些内容,可能会引起警告。不要想什么向后兼容性了。
稳定性(Stability): 1 - 试验这部分内容最近刚引进,将来的版本可能会改变也可能会被移除。你可以试试并提供反馈。如果你用到的部分对你来说非常重要,可以告诉 node 的核心团队。
稳定性(Stability): 2 - 不稳定这部分 API 正在调整中,还没在实际工作测试中达到满意的程度。如果合理的话会保证向后兼容性。
稳定性(Stability): 3 - 稳定这部分 API 验证过基本能令人满意,但是清理底层代码时可能会引起小的改变。保证向后的兼容性。
稳定性(Stability): 4 - API 冻结这部分的 API 已经在产品中广泛试验,不太可能被改变。
稳定性(Stability): 5 - 锁定除非发现了严重 bug,否则这部分代码永远不会改变。请不要对这部分内容提出更改建议,否则会被拒绝。

JSON 输出

稳定性(Stability): 1 - 试验

每个通过 markdown 生成的 HTML 文件都有相应的 JSON 文件。

这个特性从 v0.6.12 开始,是试验性功能。

更新日期更新内容
2015-04-21第一版发布,翻译自 Node.js V0.12.2 官方文档

相关教程

JSON教程


第一个服务器的例子就从 “Hello World” 开始:

var http = require('http');http.createServer(function (request, response) {  response.writeHead(200, {'Content-Type': 'text/plain'});  response.end('Hello World
');}).listen(8124);console.log('Server running at http://127.0.0.1:8124/');

把代码拷贝到example.js文件里,使用node程序执行:

> node example.jsServer running at http://127.0.0.1:8124/

在该Node.js官方文档中的所有的例子都可以使用上述方法执行。


Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。

稳定性: 5 - 锁定

这个模块可用于应用的单元测试,通过 require('assert') 可以使用这个模块。

assert.fail(actual, expected, message, operator)

使用参数operator测试参数actual (实际值) 和expected(期望值)是否相等。

assert(value[, message]), assert.ok(value[, message])

测试参数value是否为true,此函数和assert.equal(true, !!value, message);等价。

assert.equal(actual, expected[, message])

判断实际值actual和期望值expected是否相等。

assert.notEqual(actual, expected[, message])

判断实际值actual和期望值expected是否不等。

assert.deepEqual(actual, expected[, message])

执行深度比较,判断实际值actual和期望值expected是否相等。

assert.notDeepEqual(actual, expected[, message])

深度比较两个参数是否不相等。

assert.strictEqual(actual, expected[, message])

深度比较两个参数是否相等。

assert.notStrictEqual(actual, expected[, message])

此函数使用操作符 ‘!==’ 严格比较是否两参数不相等。

assert.throws(block[, error][, message])

声明一个block用来抛出错误(error),error可以是构造函数,正则表达式或其他验证器。

使用构造函数验证实例:

    assert.throws(      function() {        throw new Error("Wrong value");      },      Error    );

使用正则表达式验证错误信息:

    assert.throws(      function() {        throw new Error("Wrong value");      },      /value/    );

用户自定义的错误验证器:

    assert.throws(      function() {        throw new Error("Wrong value");      },      function(err) {        if ( (err instanceof Error) && /value/.test(err) ) {          return true;        }      },      "unexpected error"    );

assert.doesNotThrow(block[, message])

声明block不抛出错误,详细信息参见assert.throws

assert.ifError(value)

判断参数value是否为false ,如果是true,则抛出异常。通常用来测试回调中第一个参数error。


文档: 4 - API 冻结

Node里很多对象会分发事件: 每次有连接的时候net.Server会分发事件,当文件打开的时候fs.readStream会分发事件。所有能分发事件的对象都是 events.EventEmitter的实例。通过require("events");能访问这个模块。

一般来说,事件名都遵照驼峰规则,但这不是强制规定,任何形式的字符串都可以做为事件名。

为了处理事件,通常将函数关联到对象上。这些函数也叫监听者(listeners)。在这个函数里,this指向监听者所关联的EventEmitter

类: events.EventEmitter

你可以通过require('events').EventEmitter获取EventEmitter类。

EventEmitter实例遇到错误后,通常会触发一个错误事件。错误事件在node里是特殊例子。如果没有监听者,默认的操作是打印一个堆栈信息并退出程序。

当添加新的监听者时, EventEmitters会触发'newListener'事件,当移除时会触发'removeListener'

emitter.addListener(event, listener)

emitter.on(event, listener)

添加一个监听者到特定event的监听数组的尾部,触发器不会检查是否已经添加过这个监听者。 多次调用相同的eventlistener将会导致listener添加多次。

server.on('connection', function (stream) {  console.log('someone connected!');});

返回emitter。

emitter.once(event, listener)

给事件添加一个一次性的listener,这个listener只会被触发一次,之后就会被移除。

server.once('connection', function (stream) {  console.log('Ah, we have our first user!');});

返回emitter。

emitter.removeListener(event, listener)

从一个某个事件的listener数组中移除一个listener。注意,这个操作会改变listener数组内容的次序。

var callback = function(stream) {  console.log('someone connected!');};server.on('connection', callback);// ...server.removeListener('connection', callback);

removeListener最多会移除数组里的一个listener。如果多次添加同一个listener到数组,那就需要多次调用removeListener来移除每一个实例。

返回emitter。

emitter.removeAllListeners([event])

移除所有的listener,或者某个事件listener。最好不要移除全部listener,尤其是那些不是你传入的(比如socket或文件流)。

返回emitter。

emitter.setMaxListeners(n)

默认情况下,给单个事件添加超过10个listener,事件分发器会打印警告。这样有利于检查内存泄露。不过不是所有的分发器都应该限制在10个,这个函数允许改变 listener数量,无论是0还是更多。

返回emitter。

EventEmitter.defaultMaxListeners

emitter.setMaxListeners(n)设置一个分发器的最大listener数,而这个函数会立即设置所有EventEmitter的当前值和默认值。要小心使用。

请注意,emitter.setMaxListeners(n)的优先级高于EventEmitter.defaultMaxListeners.

emitter.listeners(event)

用于返回事件的listener数组。

server.on('connection', function (stream) {  console.log('someone connected!');});console.log(util.inspect(server.listeners('connection'))); // [ [Function] ]

emitter.emit(event[, arg1][, arg2][, ...])

允许你使用指定的参数顺序的执行每一个listener.

如果事件有 listener,返回true, 否则false

类方法: EventEmitter.listenerCount(emitter, event)

返回指定事件的listener数量。

Event: 'newListener'

  • event{String}事件名
  • listener{Function}事件处理函数

添加listener的时候会触发这个事件。当这个事件触发的时候,listener可能还没添加到listener数组。

Event: 'removeListener'

  • event{String}事件名
  • listener{Function}事件处理函数

删除listener的时候会触发这个事件。当这个事件触发的时候,listener可能还还没从listener数组移除。


Punycode是根据RFC 3492标准定义的字符编码方案,主要用于把域名从地方语言所采用的Unicode编码转换成为可用于DNS系统的编码。

稳定性: 2 - 不稳定

Punycode.js从Node.js v0.6.2+开始内置. 使用require('punycode')来访问。 (要在其他Node.js版本中访问,先用npm来punycode安装)。

punycode.decode(string)

将一个纯ASCII的Punycode字符串转换为Unicode字符串。

// decode domain name partspunycode.decode('maana-pta'); // 'mañana'punycode.decode('--dqo34k'); // '☃-⌘'

punycode.encode(string)

将一个纯Unicode Punycode字符串转换为纯ASCII字符串。

// encode domain name partspunycode.encode('mañana'); // 'maana-pta'punycode.encode('☃-⌘'); // '--dqo34k'

punycode.toUnicode(domain)

将一个表示域名的Punycode字符串转换为Unicode。只有域名中的Punycode部分会被转换,也就是说你也可以在一个已经转换为Unicode的字符串上调用它。

// decode domain namespunycode.toUnicode('xn--maana-pta.com'); // 'mañana.com'punycode.toUnicode('xn----dqo34k.com'); // '☃-⌘.com'

punycode.toASCII(domain)

将一个表示域名的Unicode字符串转换为Punycode。只有域名中的非ASCII部分会被转换,也就是说你也可以在一个已经转换为ASCII的字符串上调用它。

// encode domain namespunycode.toASCII('mañana.com'); // 'xn--maana-pta.com'punycode.toASCII('☃-⌘.com'); // 'xn----dqo34k.com'

punycode.ucs2

punycode.ucs2.decode(string)

创建一个包含字符串中每个Unicode符号的数字编码点的数组。由于JavaScript在内部使用UCS-2, 该函数会按照UTF-16将一对代半数(UCS-2暴露的单独的字符)转换为单独一个编码点。

punycode.ucs2.decode('abc'); // [0x61, 0x62, 0x63]// surrogate pair for U+1D306 tetragram for centre:punycode.ucs2.decode('uD834uDF06'); // [0x1D306]

punycode.ucs2.encode(codePoints)

创建以一组数字编码点为基础一个字符串。

punycode.ucs2.encode([0x61, 0x62, 0x63]); // 'abc'punycode.ucs2.encode([0x1D306]); // 'uD834uDF06'

punycode.version

表示当前Punycode.js版本数字的字符串。


稳定性: 3 - 稳定

纯Javascript语言对Unicode友好,能够很好地处理Unicode编码的字符串数据,但是难以处理二进制数据。在处理TCP流和文件系统时经常需要操作字节流。Node提供了一些机制,用于操作、创建、以及消耗字节流。

在Node.js中提供了Buffer,它可以处理二进制以及非Unicode编码的数据。

在Buffer类实例化中存储了原始数据。Buffer类似于一个整数数组,在V8堆(the V8 heap)原始存储空间给它分配了内存。一旦创建了Buffer实例,则无法改变大小。

Buffer类是全局对象,所以访问它不必使用require('buffer')

Buffers和Javascript字符串对象之间转换时需要一个明确的编码方法。下面是字符串的不同编码:

  • 'ascii'- 7位的ASCII数据。这种编码方式非常快,它会移除最高位内容。

  • 'utf8'- 多字节编码Unicode字符。大部分网页和文档使用这类编码方式。

  • 'utf16le'- 2个或4个字节, Little Endian (LE)编码Unicode字符。编码范围(U+10000 到 U+10FFFF) 。

  • 'ucs2'-'utf16le'的子集。

  • 'base64' - Base64字符编码。

  • 'binary'- 仅使用每个字符的头8位将原始的二进制信息进行编码。在需使用Buffer的情况下,应该尽量避免使用这个已经过时的编码方式。这个编码方式将会在未来某个版本中弃用。
  • 'hex'- 每个字节都采用二进制编码。

Buffer中创建一个数组,需要注意以下规则:

  1. Buffer是内存拷贝,而不是内存共享。

  2. Buffer占用内存被解释为一个数组,而不是字节数组。比如,new Uint32Array(new Buffer([1,2,3,4]))创建了4个Uint32Array,它的成员为 [1,2,3,4],而不是[0x1020304][0x4030201]

注意:Node.js v0.8只是简单的引用了array.buffer里的buffer,而不是对其进行克隆(cloning)。

介绍一个高效的方法,ArrayBuffer#slice()拷贝了一份切片,而Buffer#slice()新建了一份。

类: Buffer

Buffer类是全局变量类型,用来直接处理二进制数据。它能够使用多种方式构建。

new Buffer(size)

  • sizeNumber类型

分配一个新的size大小单位为8位字节的buffer。

注意:size必须小于kMaxLength,否则将会抛出RangeError异常。

new Buffer(array)

  • arrayArray

使用一个8位字节array数组分配一个新的buffer。

new Buffer(buffer)

  • buffer{Buffer}

拷贝参数buffer的数据到Buffer实例。

new Buffer(str[, encoding])

  • str String类型 - 需要编码的字符串。
  • encoding String类型 - 编码方式, 可选。

分配一个新的buffer ,其中包含着传入的str字符串。encoding 编码方式默认为'utf8'

类方法: Buffer.isEncoding(encoding)

  • encoding {String} 用来测试给定的编码字符串

如果参数编码encoding是有效的,则返回true,否则返回false。

类方法: Buffer.isBuffer(obj)

  • obj对象
  • 返回:Boolean

obj如果是Buffer 返回true,否则返回 false。

类方法: Buffer.byteLength(string[, encoding])

  • string String类型
  • encoding String类型,可选的,默认为: 'utf8'
  • 返回:Number类型

将会返回这个字符串真实字节长度。 encoding编码默认是:utf8。 这和String.prototype.length不一样,因为那个方法返回这个字符串中字符的数量。

例如:

str = 'u00bd + u00bc = u00be';console.log(str + ": " + str.length + " characters, " +  Buffer.byteLength(str, 'utf8') + " bytes");// ½ + ¼ = ¾: 9 characters, 12 bytes

类方法: Buffer.concat(list[, totalLength])

  • list {Array} 用来连接的数组
  • totalLength {Number 类型} 数组里所有对象的总buffer大小

返回一个buffer对象,它将参数buffer数组中所有buffer对象拼接在一起。

如果传入的数组没有内容,或者totalLength是0,那将返回一个长度为0的buffer。

如果数组长度为1,返回数组第一个成员。

如果数组长度大于0,将会创建一个新的Buffer实例。

如果没有提供totalLength参数,会根据buffer数组计算,这样会增加一个额外的循环计算,所以提供一个准确的totalLength参数速度更快。

类方法: Buffer.compare(buf1, buf2)

  • buf1 {Buffer}
  • buf2 {Buffer}

buf1.compare(buf2)一样, 用来对数组排序:

var arr = [Buffer('1234'), Buffer('0123')];arr.sort(Buffer.compare);

buf.length

  • Number类型

返回这个buffer的bytes数。注意这未必是buffer里面内容的大小。length是buffer对象所分配的内存数,它不会随着这个buffer对象内容的改变而改变。

buf = new Buffer(1234);console.log(buf.length);buf.write("some string", 0, "ascii");console.log(buf.length);// 1234// 1234

length不能改变,如果改变length将会导致不可以预期的结果。如果想要改变buffer的长度,需要使用buf.slice来创建新的buffer。

buf = new Buffer(10);buf.write("abcdefghj", 0, "ascii");console.log(buf.length); // 10buf = buf.slice(0,5);console.log(buf.length); // 5

buf.write(string[, offset][, length][, encoding])

  • string String类型 - 写到buffer里
  • offset Number类型,可选参数,默认值: 0
  • length Number类型,可选参数,默认值:buffer.length - offset
  • encoding String类型,可选参数,默认值: 'utf8'

根据参数offset偏移量和指定的encoding编码方式,将参数string数据写入buffer。offset偏移量默认值是0,encoding编码方式默认是utf8。 length长度是将要写入的字符串的bytes大小。返回number类型,表示写入了多少8位字节流。如果buffer没有足够的空间来放整个string,它将只会只写入部分字符串。length默认是 buffer.length - offset。 这个方法不会出现写入部分字符。

buf = new Buffer(256);len = buf.write('u00bd + u00bc = u00be', 0);console.log(len + " bytes: " + buf.toString('utf8', 0, len));

buf.writeUIntLE(value, offset, byteLength[, noAssert])

buf.writeUIntBE(value, offset, byteLength[, noAssert])

buf.writeIntLE(value, offset, byteLength[, noAssert])

buf.writeIntBE(value, offset, byteLength[, noAssert])

  • value {Number 类型}准备写到buffer字节数
  • offset {Number 类型} 0 <= offset <= buf.length
  • byteLength {Number 类型} 0 < byteLength <= 6
  • noAssert {Boolean} 默认值: false
  • 返回: {Number 类型}

value写入到buffer里, 它由offsetbyteLength决定,支持48位计算,例如:

var b = new Buffer(6);b.writeUIntBE(0x1234567890ab, 0, 6);// <Buffer 12 34 56 78 90 ab>

noAssert值为true时,不再验证valueoffset 的有效性。 默认是false

buf.readUIntLE(offset, byteLength[, noAssert])

buf.readUIntBE(offset, byteLength[, noAssert])

buf.readIntLE(offset, byteLength[, noAssert])

buf.readIntBE(offset, byteLength[, noAssert])

  • offset {Number 类型} 0 <= offset <= buf.length
  • byteLength {Number 类型} 0 < byteLength <= 6
  • noAssert {Boolean} 默认值: false
  • 返回: {Number 类型}

支持48位以下的数字读取。 例如:

var b = new Buffer(6);b.writeUint16LE(0x90ab, 0);b.writeUInt32LE(0x12345678, 2);b.readUIntLE(0, 6).toString(16);  // 指定为 6 bytes (48 bits)// 输出: '1234567890ab'

noAssert 值为true时, offset不再验证是否超过buffer的长度,默认为false

buf.toString([encoding][, start][, end])

  • encoding String 类型,可选参数,默认值: 'utf8'
  • start Number 类型,可选参数,默认值: 0
  • end Number 类型,可选参数,默认值: buffer.length

根据encoding参数(默认是 'utf8')返回一个解码过的string类型。还会根据传入的参数start(默认是 0)和end (默认是 buffer.length)作为取值范围。

buf = new Buffer(26);for (var i = 0 ; i < 26 ; i++) {  buf[i] = i + 97; // 97 is ASCII a}buf.toString('ascii'); // 输出: abcdefghijklmnopqrstuvwxyzbuf.toString('ascii',0,5); // 输出: abcdebuf.toString('utf8',0,5); // 输出: abcdebuf.toString(undefined,0,5); // encoding defaults to 'utf8', 输出 abcde

查看上面buffer.write()例子。

buf.toJSON()

返回一个JSON表示的Buffer实例。JSON.stringify 将会默认调用字符串序列化这个Buffer实例。

例如:

var buf = new Buffer('test');var json = JSON.stringify(buf);console.log(json);// '{"type":"Buffer","data":[116,101,115,116]}'var copy = JSON.parse(json, function(key, value) {    return value && value.type === 'Buffer'      ? new Buffer(value.data)      : value;  });console.log(copy);// <Buffer 74 65 73 74>

buf[index]

获取或设置指定index位置的8位字节。这个值是指单个字节,所以必须在合法的范围取值,16进制的0x00到0xFF,或者0到255。

例如: 拷贝一个ASCII编码的string字符串到一个buffer,一次一个byte进行拷贝:

str = "node.js";buf = new Buffer(str.length);for (var i = 0; i < str.length ; i++) {  buf[i] = str.charCodeAt(i);}console.log(buf);// node.js

buf.equals(otherBuffer)

  • otherBuffer {Buffer}

如果 thisotherBuffer 拥有相同的内容,返回true。

buf.compare(otherBuffer)

  • otherBuffer {Buffer}

返回一个数字,表示 thisotherBuffer 之前,之后或相同。

buf.copy(targetBuffer[, targetStart][, sourceStart][, sourceEnd])

  • targetBuffer Buffer 对象 - Buffer to copy into
  • targetStart Number 类型,可选参数, 默认值: 0
  • sourceStart Number 类型,可选参数, 默认值: 0
  • sourceEnd Number 类型,可选参数, 默认值: buffer.length

buffer拷贝,源和目标可以相同。targetStart目标开始偏移和sourceStart源开始偏移默认都是0。sourceEnd源结束位置偏移默认是源的长度 buffer.length

例如:创建2个Buffer,然后把buf1的16到19位内容拷贝到buf2第8位之后。

buf1 = new Buffer(26);buf2 = new Buffer(26);for (var i = 0 ; i < 26 ; i++) {  buf1[i] = i + 97; // 97 is ASCII a  buf2[i] = 33; // ASCII !}buf1.copy(buf2, 8, 16, 20);console.log(buf2.toString('ascii', 0, 25));// !!!!!!!!qrst!!!!!!!!!!!!!

例如: 在同一个buffer中,从一个区域拷贝到另一个区域:

buf = new Buffer(26);for (var i = 0 ; i < 26 ; i++) {  buf[i] = i + 97; // 97 is ASCII a}buf.copy(buf, 0, 4, 10);console.log(buf.toString());// efghijghijklmnopqrstuvwxyz

buf.slice([start][, end])

  • start Number类型,可选参数,默认值: 0
  • end Number类型,可选参数,默认值: buffer.length

返回一个新的buffer,这个buffer将会和老的buffer引用相同的内存地址,根据start(默认是 0 ) 和end (默认是 buffer.length ) 偏移和裁剪了索引。 负的索引是从buffer尾部开始计算的。

修改这个新的buffer实例slice切片,也会改变原来的buffer!

例如: 创建一个ASCII字母的Buffer,进行slice切片,然后修改源Buffer上的一个byte。

var buf1 = new Buffer(26);for (var i = 0 ; i < 26 ; i++) {  buf1[i] = i + 97; // 97 is ASCII a}var buf2 = buf1.slice(0, 3);console.log(buf2.toString('ascii', 0, buf2.length));buf1[0] = 33;console.log(buf2.toString('ascii', 0, buf2.length));// abc// !bc

buf.readUInt8(offset[, noAssert])

  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false
  • 返回: Number类型

从这个buffer对象里,根据指定的偏移量,读取一个有符号8位整数 整形。

若参数noAssert为true将不会验证offset偏移量参数。 如果这样offset 可能会超出buffer的末尾。默认是false

例如:

var buf = new Buffer(4);buf[0] = 0x3;buf[1] = 0x4;buf[2] = 0x23;buf[3] = 0x42;for (ii = 0; ii < buf.length; ii++) {  console.log(buf.readUInt8(ii));}// 0x3// 0x4// 0x23// 0x42

buf.readUInt16LE(offset[, noAssert])

buf.readUInt16BE(offset[, noAssert])

  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false
  • 返回: Number类型

从buffer对象里,根据指定的偏移量,使用特殊的endian字节序格式读取一个有符号16位整数。

若参数noAssert为true将不会验证offset偏移量参数。 这意味着offset可能会超出buffer的末尾。默认是false

例如:

var buf = new Buffer(4);buf[0] = 0x3;buf[1] = 0x4;buf[2] = 0x23;buf[3] = 0x42;console.log(buf.readUInt16BE(0));console.log(buf.readUInt16LE(0));console.log(buf.readUInt16BE(1));console.log(buf.readUInt16LE(1));console.log(buf.readUInt16BE(2));console.log(buf.readUInt16LE(2));// 0x0304// 0x0403// 0x0423// 0x2304// 0x2342// 0x4223

buf.readUInt32LE(offset[, noAssert])

buf.readUInt32BE(offset[, noAssert])

  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false
  • 返回: Number类型

从这个buffer对象里,根据指定的偏移量,使用指定的endian字节序格式读取一个有符号32位整数。

若参数noAssert为true将不会验证offset偏移量参数。 这意味着offset可能会超出buffer的末尾。默认是false

例如:

var buf = new Buffer(4);buf[0] = 0x3;buf[1] = 0x4;buf[2] = 0x23;buf[3] = 0x42;console.log(buf.readUInt32BE(0));console.log(buf.readUInt32LE(0));// 0x03042342// 0x42230403

buf.readInt8(offset[, noAssert])

  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false
  • 返回: Number类型

从这个buffer对象里,根据指定的偏移量,读取一个signed 8位整数。

若参数noAssert为true将不会验证offset偏移量参数。 这意味着offset可能会超出buffer的末尾。默认是false

返回和buffer.readUInt8一样,除非buffer中包含了有作为2的补码的有符号值。

buf.readInt16LE(offset[, noAssert])

buf.readInt16BE(offset[, noAssert])

  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false
  • 返回: Number类型

从这个buffer对象里,根据指定的偏移量,使用特殊的endian格式读取一个signed 16位整数。

若参数noAssert为true将不会验证 offset 偏移量参数。 这意味着offset可能会超出buffer的末尾。默认是false

返回和buffer.readUInt16一样,除非buffer中包含了有作为2的补码的有符号值。

buf.readInt32LE(offset[, noAssert])

buf.readInt32BE(offset[, noAssert])

  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false
  • 返回: Number类型

从这个buffer对象里,根据指定的偏移量,使用指定的endian字节序格式读取一个signed 32位整数。

若参数noAssert为true将不会验证offset偏移量参数。 这意味着offset可能会超出buffer的末尾。默认是false

buffer.readUInt32一样返回,除非buffer中包含了有作为2的补码的有符号值。

buf.readFloatLE(offset[, noAssert])

buf.readFloatBE(offset[, noAssert])

  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false
  • 返回: Number类型

从这个buffer对象里,根据指定的偏移量,使用指定的endian字节序格式读取一个32位浮点数。

若参数noAssert为true将不会验证offset偏移量参数。 这意味着offset可能会超出buffer的末尾。默认是false

例如:

var buf = new Buffer(4);buf[0] = 0x00;buf[1] = 0x00;buf[2] = 0x80;buf[3] = 0x3f;console.log(buf.readFloatLE(0));// 0x01

buf.readDoubleLE(offset[, noAssert])

buf.readDoubleBE(offset[, noAssert])

  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false
  • 返回: Number类型

从这个buffer对象里,根据指定的偏移量,使用指定的endian字节序格式读取一个64位double。

若参数noAssert为true将不会验证offset偏移量参数。 这意味着offset可能会超出buffer 的末尾。默认是false

例如:

var buf = new Buffer(8);buf[0] = 0x55;buf[1] = 0x55;buf[2] = 0x55;buf[3] = 0x55;buf[4] = 0x55;buf[5] = 0x55;buf[6] = 0xd5;buf[7] = 0x3f;console.log(buf.readDoubleLE(0));// 0。3333333333333333

buf.writeUInt8(value, offset[, noAssert])

  • value Number类型
  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false

根据传入的offset偏移量将value写入buffer。注意:value必须是一个合法的有符号 8 位整数。

若参数noAssert为true将不会验证offset 偏移量参数。 这意味着value可能过大,或者offset可能会超出buffer的末尾从而造成value被丢弃。 除非你对这个参数非常有把握,否则不要使用。默认是false

例如:

var buf = new Buffer(4);buf.writeUInt8(0x3, 0);buf.writeUInt8(0x4, 1);buf.writeUInt8(0x23, 2);buf.writeUInt8(0x42, 3);console.log(buf);// <Buffer 03 04 23 42>

buf.writeUInt16LE(value, offset[, noAssert])

buf.writeUInt16BE(value, offset[, noAssert])

  • value Number类型
  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false

根据传入的offset偏移量和指定的endian格式将value写入buffer。注意:value必须是一个合法的有符号16位整数。

若参数noAssert为true将不会验证valueoffset偏移量参数。 这意味着value可能过大,或者offset可能会超出buffer的末尾从而造成value被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是false

例如:

var buf = new Buffer(4);buf.writeUInt16BE(0xdead, 0);buf.writeUInt16BE(0xbeef, 2);console.log(buf);buf.writeUInt16LE(0xdead, 0);buf.writeUInt16LE(0xbeef, 2);console.log(buf);// <Buffer de ad be ef>// <Buffer ad de ef be>

buf.writeUInt32LE(value, offset[, noAssert])

buf.writeUInt32BE(value, offset[, noAssert])

  • value Number类型
  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false

根据传入的offset偏移量和指定的endian格式将value写入buffer。注意:value必须是一个合法的有符号32位整数。

若参数noAssert为true将不会验证valueoffset偏移量参数。 这意味着value可能过大,或者offset可能会超出buffer的末尾从而造成value被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是false

例如:

var buf = new Buffer(4);buf.writeUInt32BE(0xfeedface, 0);console.log(buf);buf.writeUInt32LE(0xfeedface, 0);console.log(buf);// <Buffer fe ed fa ce>// <Buffer ce fa ed fe>

buf.writeInt8(value, offset[, noAssert])

  • value Number类型
  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false

根据传入的offset偏移量将value写入buffer。注意:value必须是一个合法的signed 8位整数。

若参数noAssert为true将不会验证valueoffset偏移量参数。这意味着value可能过大,或者offset可能会超出buffer的末尾从而造成value被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是false

buffer.writeUInt8一样工作,除非是把有2的补码的有符号整数有符号整形写入buffer。

buf.writeInt16LE(value, offset[, noAssert])

buf.writeInt16BE(value, offset[, noAssert])

  • value Number类型
  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false

根据传入的offset偏移量和指定的endian格式将value写入buffer。注意:value必须是一个合法的signed 16位整数。

若参数noAssert为true将不会验证valueoffset偏移量参数。 这意味着value可能过大,或者offset可能会超出buffer的末尾从而造成value被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是false

buffer.writeUInt16*一样工作,除非是把有2的补码的有符号整数 有符号整形写入buffer

buf.writeInt32LE(value, offset[, noAssert])

buf.writeInt32BE(value, offset[, noAssert])

  • value Number类型
  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false

根据传入的offset偏移量和指定的endian格式将value写入buffer。注意:value必须是一个合法的signed 32位整数。

若参数noAssert为true将不会验证valueoffset偏移量参数。 这意味着value可能过大,或者offset可能会超出 buffer 的末尾从而造成value被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是false

buffer.writeUInt32*一样工作,除非是把有2的补码的有符号整数、有符号整形写入buffer。

buf.writeFloatLE(value, offset[, noAssert])

buf.writeFloatBE(value, offset[, noAssert])

  • value Number类型
  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false

根据传入的offset偏移量和指定的endian格式将value写入buffer。注意:当value不是一个32位浮点数类型的值时,结果将是不确定的。

若参数noAssert为true将不会验证valueoffset偏移量参数。这意味着value可能过大,或者offset可能会超出buffer的末尾从而造成value被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是false

例如:

var buf = new Buffer(4);buf.writeFloatBE(0xcafebabe, 0);console.log(buf);buf.writeFloatLE(0xcafebabe, 0);console.log(buf);// <Buffer 4f 4a fe bb>// <Buffer bb fe 4a 4f>

buf.writeDoubleLE(value, offset[, noAssert])

buf.writeDoubleBE(value, offset[, noAssert])

  • value Number类型
  • offset Number类型
  • noAssert Boolean,可选参数, 默认值: false

根据传入的offset偏移量和指定的endian格式将value写入buffer。注意:value必须是一个有效的64位double类型的值。

若参数noAssert为true将不会验证valueoffset偏移量参数。 这意味着value可能过大,或者offset可能会超出buffer的末尾从而造成value被丢弃。 除非你对这个参数非常有把握,否则尽量不要使用。默认是false

例如:

var buf = new Buffer(8);buf.writeDoubleBE(0xdeadbeefcafebabe, 0);console.log(buf);buf.writeDoubleLE(0xdeadbeefcafebabe, 0);console.log(buf);// <Buffer 43 eb d5 b7 dd f9 5f d7>// <Buffer d7 5f f9 dd b7 d5 eb 43>

buf.fill(value[, offset][, end])

  • value
  • offset Number类型, Optional
  • end Number类型, Optional

使用指定的value来填充这个buffer。如果没有指定offset (默认是 0) 并且end(默认是buffer.length) ,将会填充整个buffer。

var b = new Buffer(50);b.fill("h");

buffer.iNSPECT_MAX_BYTES

  • Number 类型,默认值: 50

设置当调用buffer.inspect()方法后,将会返回多少bytes 。用户模块重写这个值可以。

注意这个属性是require('buffer')模块返回的。这个属性不是在全局变量Buffer中,也不在buffer的实例里。

类: SlowBuffer

返回一个不被池管理的Buffer

大量独立分配的Buffer容易带来垃圾,为了避免这个情况,小于4KB的空间都是切割自一个较大的独立对象。这种策略既提高了性能也改善了内存使用率。V8不需要跟踪和清理过多的Persistent对象。

当开发者需要将池中一小块数据保留一段时间,比较好的办法是用SlowBuffer创建一个不被池管理的Buffer实例,并将相应数据拷贝出来,如下所示:

// need to keep around a few small chunks of memoryvar store = [];socket。on('readable', function() {  var data = socket。read();  // allocate for retained data  var sb = new SlowBuffer(10);  // copy the data into the new allocation  data。copy(sb, 0, 0, 10);  store。push(sb);});

请谨慎使用,仅作为经常发现他们的应用中过度的内存保留时的最后手段。


Node.js官方文档对Node.js文件系统进行了详细的介绍。

稳定性: 3 - 稳定

Node.js文件系统模块是一个封装了标准的POSIX文件I/O操作的集合。通过require('fs')使用这个模块,其中所有的方法都有同步和异步两种模式。

异步方法最后一个参数都是回调函数,这个回调的参数取决于方法,不过第一个参数一般都是异常。如果操作成功,那么第一个参数就是nullundefined

当使用一个同步操作的时候,任意的异常都立即抛出,可以用try/catch来处理异常,使得程序正常运行。

以下是一个异步操作的例子:

var fs = require('fs');fs.unlink('/tmp/hello', function (err) {  if (err) throw err;  console.log('successfully deleted /tmp/hello');});

以下是一个同步操作的例子:

var fs = require('fs');fs.unlinkSync('/tmp/hello');console.log('successfully deleted /tmp/hello');

异步方法不能保证操作顺序,因此下面的例子很容易出错:

fs.rename('/tmp/hello', '/tmp/world', function (err) {  if (err) throw err;  console.log('renamed complete');});fs.stat('/tmp/world', function (err, stats) {  if (err) throw err;  console.log('stats: ' + JSON.stringify(stats));});

该例子出错的原因很可能是因为先执行了fs.stat方法,正确的方法如下:

fs.rename('/tmp/hello', '/tmp/world', function (err) {  if (err) throw err;  fs.stat('/tmp/world', function (err, stats) {    if (err) throw err;    console.log('stats: ' + JSON.stringify(stats));  });});

在繁忙的进程里,强烈建议使用异步方法。同步方法会阻塞整个进程,直到方法完成。

可能会用到相对路径,路径是相对process.cwd()来说的。

大部分fs函数会忽略回调参数,如果忽略,将会用默认函数抛出异常。如果想得到原调用点的堆栈信息,需要设置环境变量NODE_DEBUG:

$ cat script.jsfunction bad() {  require('fs').readFile('/');}bad();$ env NODE_DEBUG=fs node script.jsfs.js:66        throw err;              ^Error: EISDIR, read    at rethrow (fs.js:61:21)    at maybeCallback (fs.js:79:42)    at Object.fs.readFile (fs.js:153:18)    at bad (/path/to/script.js:2:17)    at Object.<anonymous> (/path/to/script.js:5:1)    <etc.>

fs.rename(oldPath, newPath, callback)

异步函数rename(2)。回调函数只有一个参数:可能出现的异常。

fs.renameSync(oldPath, newPath)

同步函数rename(2)。 返回undefined

fs.ftruncate(fd, len, callback)

异步函数ftruncate(2)。 回调函数只有一个参数:可能出现的异常。

fs.ftruncateSync(fd, len)

同步函数ftruncate(2)。 返回undefined

fs.truncate(path, len, callback)

异步函数truncate(2)。 回调函数只有一个参数:可能出现的异常。 文件描述符也可以作为第一个参数,如果这种情况,调用fs.ftruncate()

fs.truncateSync(path, len)

同步函数truncate(2)。 返回undefined

fs.chown(path, uid, gid, callback)

异步函数chown(2)。回调函数只有一个参数:可能出现的异常。

fs.chownSync(path, uid, gid)

同步函数chown(2)。返回undefined

fs.fchown(fd, uid, gid, callback)

异步函数fchown(2)。回调函数只有一个参数:可能出现的异常。

fs.fchownSync(fd, uid, gid)

同步函数 fchown(2)。返回undefined

fs.lchown(path, uid, gid, callback)

异步函数lchown(2)。回调函数只有一个参数:可能出现的异常。

fs.lchownSync(path, uid, gid)

同步函数lchown(2)。返回undefined

fs.chmod(path, mode, callback)

异步函数chmod(2)。回调函数只有一个参数:可能出现的异常。

fs.chmodSync(path, mode)

同步函数chmod(2)。返回 undefined

fs.fchmod(fd, mode, callback)

异步函数fchmod(2)。回调函数只有一个参数:可能出现的异常。

fs.fchmodSync(fd, mode)

同步函数fchmod(2)。返回undefined

fs.lchmod(path, mode, callback)

异步函数 lchmod(2)。回调函数只有一个参数:可能出现的异常。

仅在Mac OS X可用。

fs.lchmodSync(path, mode)

同步函数lchmod(2)。返回undefined

fs.stat(path, callback)

异步函数stat(2)。回调函数有两个参数:(err, stats) ,其中stats是一个fs.Stats对象。 详情请参考fs.Stats。

fs.lstat(path, callback)

异步函数lstat(2)。回调函数有两个参数:(err, stats) ,其中stats是一个fs.Stats对象。lstat()stat()基本相同,区别在于,如果path是链接,读取的是链接本身,而不是它所链接到的文件。

fs.fstat(fd, callback)

异步函数fstat(2)。回调函数有两个参数: (err, stats),其中stats是一个fs.Stats对象。

fs.statSync(path)

同步函数stat(2)。返回fs.Stats实例。

fs.lstatSync(path)

同步函数lstat(2)。返回fs.Stats实例。

fs.fstatSync(fd)

同步函数fstat(2)。返回fs.Stats实例。

fs.link(srcpath, dstpath, callback)

异步函数link(2)。回调函数只有一个参数:可能出现的异常。

fs.linkSync(srcpath, dstpath)

同步函数link(2)。返回undefined

fs.symlink(srcpath, dstpath[, type], callback)

异步函数symlink(2)。回调函数只有一个参数:可能出现的异常。

type可能是'dir','file', 或'junction' (默认'file') ,仅在Windows(不考虑其他系统)有效。注意, Windows junction要求目的地址需要绝对的。当使用'junction'的时候,destination参数将会自动转换为绝对路径。

fs.symlinkSync(srcpath, dstpath[, type])

同步函数symlink(2)。 返回undefined

fs.readlink(path, callback)

异步函数readlink(2)。回调函数有2个参数(err, linkString).

fs.readlinkSync(path)

同步函数readlink(2)。返回符号链接的字符串值。

fs.realpath(path[, cache], callback)

异步函数realpath(2)。回调函数有2个参数(err,resolvedPath)。可以使用process.cwd来解决相对路径问题。

例如:

var cache = {'/etc':'/private/etc'};fs.realpath('/etc/passwd', cache, function (err, resolvedPath) {  if (err) throw err;  console.log(resolvedPath);});

fs.realpathSync(path[, cache])

同步函数realpath(2)。返回解析出的路径。

fs.unlink(path, callback)

异步函数unlink(2)。回调函数只有一个参数:可能出现的异常.

fs.unlinkSync(path)

同步函数unlink(2)。返回undefined

fs.rmdir(path, callback)

异步函数rmdir(2)。回调函数只有一个参数:可能出现的异常.

fs.rmdirSync(path)

同步函数rmdir(2)。返回undefined

fs.mkdir(path[, mode], callback)

异步函数mkdir(2)。回调函数只有一个参数:可能出现的异常. mode默认为0777.

fs.mkdirSync(path[, mode])

同步函数mkdir(2)。返回undefined

fs.readdir(path, callback)

异步函数readdir(3)。读取文件夹的内容。回调有2个参数 (err, files)files是文件夹里除了名字为'.'和'..'之外的所有文件名。

fs.readdirSync(path)

同步函数readdir(3)。返回除了文件名为'.''..'之外的所有文件.

fs.close(fd, callback)

异步函数close(2)。回调函数只有一个参数:可能出现的异常.

fs.closeSync(fd)

同步函数close(2)。返回 undefined

fs.open(path, flags[, mode], callback)

异步函数file open. 参见open(2)。flags是:

  • 'r'- 以只读模式打开;如果文件不存在,抛出异常。

  • 'r+'-以读写模式打开;如果文件不存在,抛出异常。

  • 'rs'- 同步的,以只读模式打开;指令绕过操作系统直接使用本地文件系统缓存。这个功能主要用来打开NFS挂载的文件,因为它能让你跳过可能过时的本地缓存。如果对I/O性能很在乎,就不要使用这个标志位。

    这里不是调用fs.open()变成同步阻塞请求,如果你想要这样,可以调用fs.openSync()

  • 'rs+'- 同步模式下以读写方式打开文件。注意事项参见'rs'.

  • 'w'- 以只写模式打开。文件会被创建 (如果文件不存在) 或者覆盖 (如果存在)。

  • 'wx'- 和'w'类似,如果文件存储操作失败

  • 'w+'- 以可读写方式打开。文件会被创建 (如果文件不存在) 或者覆盖 (如果存在)

  • 'wx+'- 和'w+'类似,如果文件存储操作失败。

  • 'a'- 以附加的形式打开。如果文件不存在则创建一个。

  • 'ax'- 和'a'类似,如果文件存储操作失败。

  • 'a+'- 以只读和附加的形式打开文件.若文件不存在,则会建立该文件

  • 'ax+'- 和'a+'类似,如果文件存储操作失败.

如果文件存在,参数mode设置文件模式 (permission和sticky bits)。 默认是0666,可读写。

回调有2个参数(err, fd).

排除标记'x'(对应open(2)的O_EXCL标记) 保证path是新创建的。在POSIX系统里,即使文件不存在,也会被认定为文件存在。排除标记不能确定在网络文件系统中是否有效。

Linux系统里,无法对以追加模式打开的文件进行指定位置写。系统核心忽略了位置参数,每次把数据写到文件的最后。

fs.openSync(path, flags[, mode])

fs.open()的同步版本. 返回整数形式的文件描述符。.

fs.utimes(path, atime, mtime, callback)

改变指定路径文件的时间戳。

fs.utimesSync(path, atime, mtime)

fs.utimes()的同步版本。返回undefined

fs.futimes(fd, atime, mtime, callback)

改变传入的文件描述符指向文件的时间戳。

fs.futimesSync(fd, atime, mtime)

fs.futimes()的同步版本。返回undefined

fs.fsync(fd, callback)

异步函数fsync(2)。回调函数只有一个参数:可能出现的异常.

fs.fsyncSync(fd)

同步fsync(2)。返回undefined

fs.write(fd, buffer, offset, length[, position], callback)

buffer写到fd指定的文件里。

参数offsetlength确定写哪个部分的缓存。

参数position是要写入的文件位置。如果typeof position !== 'number',将会在当前位置写入。参见pwrite(2)。

回调函数有三个参数(err, written, buffer)written指定buffer的多少字节用来写。

注意,如果fs.write 的回调还没执行,就多次调用fs.write,这样很不安全。因此,推荐使用fs.createWriteStream

Linux系统里,无法对以追加模式打开的文件进行指定位置写。系统核心忽略了位置参数,每次把数据写到文件的最后。

fs.write(fd, data[, position[, encoding]], callback)

buffer写到fd指定的文件里。如果data不是buffer,那么它就会被强制转换为字符串。

参数position是要写入的文件位置。如果typeof position !== 'number',将会在当前位置写入。参见pwrite(2)。

参数encoding :字符串的编码方式.

回调函数有三个参数(err, written, buffer)written指定buffer的多少字节用来写。注意写入的字节(bytes)和字符(string characters)不同。参见Buffer.byteLength

和写入buffer不同,必须写入整个字符串,不能截取字符串。这是因为返回的字节的位移跟字符串的位移是不一样的。

注意,如果fs.write的回调还没执行,就多次调用fs.write,这样很不安全。因此,推荐使用fs.createWriteStream

Linux系统里,无法对以追加模式打开的文件进行指定位置写。系统核心忽略了位置参数,每次把数据写到文件的最后。

fs.writeSync(fd, buffer, offset, length[, position])

fs.writeSync(fd, data[, position[, encoding]])

fs.write()的同步版本. 返回要写的bytes数.

fs.read(fd, buffer, offset, length, position, callback)

读取fd指定文件的数据。

buffer是缓冲区,数据将会写入到这里.

offset写入的偏移量

length需要读的文件长度

position读取的文件起始位置,如果positionnull, 将会从当前位置读。

回调函数有3个参数,(err, bytesRead, buffer).

fs.readSync(fd, buffer, offset, length, position)

fs.read的同步版本。返回bytesRead的数量.

fs.readFile(filename[, options], callback)

  • filename {String}
  • options {Object}
    • encoding {String | Null} 默认 = null
    • flag {String} 默认 = 'r'
  • callback {Function}

异步读取整个文件的内容。例如:

fs.readFile('/etc/passwd', function (err, data) {  if (err) throw err;  console.log(data);});

回调函数有2个参数(err, data),参数data是文件的内容。如果没有指定参数encoding,返回原生buffer

fs.readFileSync(filename[, options])

fs.readFile的同步版本. 返回整个文件的内容.

如果没有指定参数encoding,返回buffer。

fs.writeFile(filename, data[, options], callback)

  • filename {String}
  • data {String | Buffer}
  • options {Object}
    • encoding {String | Null} 默认 = 'utf8'
    • mode {Number} 默认 = 438 (aka 0666 in Octal)
    • flag {String} 默认 = 'w'
  • callback {Function}

异步写文件,如果文件已经存在则替换。data可以是缓存或者字符串。

如果参数data是buffer,会忽略参数encoding。默认值是'utf8'

列如:

fs.writeFile('message.txt', 'Hello Node', function (err) {  if (err) throw err;  console.log('It's saved!');});

fs.writeFileSync(filename, data[, options])

fs.writeFile的同步版本。返回undefined

fs.appendFile(filename, data[, options], callback)

  • filename {String}
  • data {String | Buffer}
  • options {Object}
    • encoding {String | Null} 默认 = 'utf8'
    • mode {Number} 默认 = 438 (aka 0666 in Octal)
    • flag {String} 默认 = 'a'
  • callback {Function}

异步的给文件添加数据,如果文件不存在,就创建一个。data可以是缓存或者字符串。

例如:

fs.appendFile('message.txt', 'data to append', function (err) {  if (err) throw err;  console.log('The "data to append" was appended to file!');});

fs.appendFileSync(filename, data[, options])

fs.appendFile的同步版本。返回undefined

fs.watchFile(filename[, options], listener)

稳定性: 2 - 不稳定。  尽可能的用 fs.watch 来替换。

监视filename文件的变化。每当文件被访问的时候都会调用listener

第二个参数可选。如果有,它必须包含两个boolean参数(persistentinterval)的对象。persistent指定文件被监视时进程是否继续运行。interval指定了查询文件的间隔,以毫秒为单位。缺省值为{ persistent: true, interval: 5007 }。

listener有两个参数,第一个为文件现在的状态,第二个为文件的前一个状态:

fs.watchFile('message.text', function (curr, prev) {  console.log('the current mtime is: ' + curr.mtime);  console.log('the previous mtime was: ' + prev.mtime);});

listener中的文件状态对象类型为fs.Stat。

如果想修改文件时被通知,而不是访问的时候就通知,可以比较curr.mtimeprev.mtime

fs.unwatchFile(filename[, listener])

稳定性: 2 - 不稳定. 尽可能的用 fs.watch 来替换。

停止监视filename文件的变化。如果指定了listener,那只会移除这个listener。否则,移除所有的listener,并会停止监视filename

调用fs.unwatchFile()停止监视一个没被监视的文件,不会触发错误,而会发生一个no-op。

fs.watch(filename[, options][, listener])

稳定性: 2 - 不稳定.

观察filename指定的文件或文件夹的改变。返回对象是 fs.FSWatcher

第二个参数可选。如果有,它必须是包含两个boolean参数(persistentrecursive)的对象。persistent指定文件被监视时进程是否继续运行。 recursive表明是监视所有的子文件夹还是当前文件夹,这个参数只有监视对象是文件夹时才有效,而且仅在支持的系统里有效(参见下面注意事项)。

默认值{ persistent: true, recursive: false }.

回调函数有2个参数(event, filename)eventrenamechangefilename是触发事件的文件名。

注意事项

fs.watchAPI 不是100%的跨平台兼容,可能在某些情况下不可用。

recursive参数仅在OS X上可用。仅FSEvents支持这个类型文件的监视,所以未来也不太可能有新的平台加入。

可用性

这些特性依赖于底层系统提供文件系统变动的通知。

  • Linux系统,使用inotify.
  • BSD系统,使用kqueue.
  • OS X,文件使用kqueue,文件夹使用FSEvents.
  • SunOS 系统(包括Solaris和SmartOS),使用event ports.
  • Windows系统,依赖与ReadDirectoryChangesW.

如果底层系统函数不可用,那么fs.watch就无法工作。例如,监视网络文件系统(NFS、 SMB等)经常不能用。你仍然可以用fs.watchFile查询,但是会比较慢,且不可靠。

文件名参数

回调函数中提供文件名参数,不是每个平台都能用(Linux和Windows就不行)。即使在可用的平台,也不能保证都能提供。所以不要假设回调函数中filename参数有效,要在代码里添加一些为空的逻辑判断。

fs.watch('somedir', function (event, filename) {  console.log('event is: ' + event);  if (filename) {    console.log('filename provided: ' + filename);  } else {    console.log('filename not provided');  }});

fs.exists(path, callback)

判断文件是否存在,回调函数参数是bool值。例如:

fs.exists('/etc/passwd', function (exists) {  util.debug(exists ? "it's there" : "no passwd!");});

fs.exists()是老版本的函数,因此在代码里不要用。

另外,打开文件前判断是否存在有漏洞,在fs.exists()fs.open()调用中间,另外一个进程有可能已经移除了文件。最好用fs.open()来打开文件,根据回调函数来判断是否有错误。

fs.exists()未来会被移除。

fs.existsSync(path)

fs.exists()的同步版本. 如果文件存在返回true, 否则返回false

fs.existsSync()未来会被移除。

fs.access(path[, mode], callback)

测试由参数path指向的文件的用户权限。可选参数mode为整数,它表示需要检查的权限。下面列出了所有值。mode可以是单个值,或者可以通过或运算,掩码运算实现多个权限检查。

  • fs.F_OK- 文件是对于进程可见,可以用来检查文件是否存在。参数mode的默认值。
  • fs.R_OK- 文件对于进程是否可读。
  • fs.W_OK- 文件对于进程是否可写。
  • fs.X_OK- 文件对于进程是否可执行。(Windows系统不可用,执行效果等同fs.F_OK

第三个参数是回调函数。如果检查失败,回调函数的参数就是响应的错误。下面的例子检查文件/etc/passwd是否能被当前的进程读写。

fs.access('/etc/passwd', fs.R_OK | fs.W_OK, function(err) {  util.debug(err ? 'no access!' : 'can read/write');});

fs.accessSync(path[, mode])

fs.access的同步版本. 如果发生错误抛出异常,否则不做任何事情。

类: fs.Stats

fs.stat(), fs.lstat()fs.fstat()以及同步版本的返回对象。

  • stats.isFile()
  • stats.isDirectory()
  • stats.isBlockDevice()
  • stats.isCharacterDevice()
  • stats.isSymbolicLink() (only valid with fs.lstat())
  • stats.isFIFO()
  • stats.isSocket()

对普通文件使用util.inspect(stats),返回的字符串和下面类似:

{ dev: 2114,  ino: 48064969,  mode: 33188,  nlink: 1,  uid: 85,  gid: 100,  rdev: 0,  size: 527,  blksize: 4096,  blocks: 8,  atime: Mon, 10 Oct 2011 23:24:11 GMT,  mtime: Mon, 10 Oct 2011 23:24:11 GMT,  ctime: Mon, 10 Oct 2011 23:24:11 GMT,  birthtime: Mon, 10 Oct 2011 23:24:11 GMT }

atime, mtime, birthtime, 和ctime都是Date的实例,需要使用合适的方法来比较这些值。通常使用getTime()来获取时间戳(毫秒,从 1 January 1970 00:00:00 UTC开始算),这个整数基本能满足任何比较条件。也有一些其他方法来显示额外信息。更多参见MDN JavaScript Reference

Stat Time Values

状态对象(stat object)有以下语义:

  • atime访问时间 - 文件的最后访问时间. mknod(2), utimes(2), 和 read(2)等系统调用可以改变.
  • mtime修改时间 - 文件的最后修改时间. mknod(2), utimes(2), 和 write(2)等系统调用可以改变.
  • ctime改变时间 - 文件状态(inode)的最后修改时间. chmod(2), chown(2),link(2), mknod(2), rename(2), unlink(2), utimes(2), read(2), 和write(2)等系统调用可以改变.
  • birthtime"Birth Time" - 文件创建时间,文件创建时生成。 在一些不提供文件birthtime的文件系统中, 这个字段会使用ctime或1970-01-01T00:00Z (ie, unix epoch timestamp 0)来填充。在Darwin和其他FreeBSD系统变体中,也将atime显式地设置成比它现在的birthtime更早的一个时间值,这个过程使用了 utimes(2)系统调用。

在Node v0.12版本之前,Windows系统里ctime有birthtime值. 注意在v.0.12版本中, ctime不再是"creation time", 而且在Unix系统中,他一直都不是。

fs.createReadStream(path[, options])

返回可读流对象 (见Readable Stream)。

options默认值如下:

{ flags: 'r',  encoding: null,  fd: null,  mode: 0666,  autoClose: true}

参数options提供startend位置来读取文件的特定范围内容,而不是整个文件。startend都在文件范围里,并从0开始,encoding'utf8', 'ascii''base64'

如果给了fd值,ReadStream将会忽略path参数,而使用文件描述,这样不会触发任何open事件。

如果autoClose为false,即使发生错误文件也不会关闭,需要你来负责关闭,避免文件描述符泄露。如果autoClose是true(默认值),遇到errorend,文件描述符将会自动关闭。

例如,从100个字节的文件里,读取最少10个字节:

fs.createReadStream('sample.txt', {start: 90, end: 99});

Class: fs.ReadStream

ReadStreamReadable Stream

Event: 'open'

  • fd{Integer} ReadStream 所使用的文件描述符。

当创建文件的ReadStream时触发。

fs.createWriteStream(path[, options])

返回一个新的写对象 (参见 Writable Stream)。

options是一个对象,默认值:

{ flags: 'w',  encoding: null,  fd: null,  mode: 0666 }

options也可以包含一个start选项,在指定文件中写入数据开始位置。 修改而不替换文件需要flags的模式指定为r+而不是默值的w。

和之前的ReadStream类似,如果fd不为空,WriteStream将会忽略path参数,转而使用文件描述,这样不会触发任何open事件。

类: fs.WriteStream

WriteStreamWritable Stream

Event: 'open'

  • fd{Integer} WriteStream所用的文件描述符

打开WriteStream file时触发。

file.bytesWritten

目前写入的字节数,不含等待写入的数据。

Class: fs.FSWatcher

fs.watch()返回的对象就是这个类.

watcher.close()

停止观察fs.FSWatcher对象中的更改。

Event: 'change'

  • event{String} fs改变的类型
  • filename{String} 改变的文件名 (if relevant/available)

当监听的文件或文件夹改变的时候触发,参见fs.watch

Event: 'error'

  • error{Error object}

错误发生时触发。

相关文章

JavaScript 错误 - throw、try 和 catch


本节为你介绍Node.js Query Strings。

稳定性: 3 - 稳定

该Node.js模块提供了一些处理query strings的工具,你可以通过以下方式访问它:

const querystring = require('querystring');

Node.js Query Strings包含的方法如下:

querystring.stringify(obj[, sep][, eq][, options])

该方法可以将一个对象序列化化为一个query string 。

可以选择重写默认的分隔符('&') 和分配符 ('=')。

Options对象可能包含encodeURIComponent属性 (默认:querystring.escape),如果需要,它可以用non-utf8编码字符串。

例子:

querystring.stringify({ foo: 'bar', baz: ['qux', 'quux'], corge: '' })// returns'foo=bar&baz=qux&baz=quux&corge='querystring.stringify({foo: 'bar', baz: 'qux'}, ';', ':')// returns'foo:bar;baz:qux'// Suppose gbkEncodeURIComponent function already exists,// it can encode string with `gbk` encodingquerystring.stringify({ w: '中文', foo: 'bar' }, null, null,  { encodeURIComponent: gbkEncodeURIComponent })// returns'w=%D6%D0%CE%C4&foo=bar'

querystring.parse(str[, sep][, eq][, options])

该方法可以将query string反序列化为对象。

你可以选择重写默认的分隔符('&') 和分配符 ('=')。

Options对象可能包含maxKeys属性(默认:1000),用来限制处理过的健值(keys)。设置为0的话,可以去掉键值的数量限制。

Options 对象可能包含decodeURIComponent属性(默认:querystring.unescape),如果需要,可以用来解码non-utf8编码的字符串。

例子:

querystring.parse('foo=bar&baz=qux&baz=quux&corge')// returns{ foo: 'bar', baz: ['qux', 'quux'], corge: '' }// Suppose gbkDecodeURIComponent function already exists,// it can decode `gbk` encoding stringquerystring.parse('w=%D6%D0%CE%C4&foo=bar', null, null,  { decodeURIComponent: gbkDecodeURIComponent })// returns{ w: '中文', foo: 'bar' }

querystring.escape

escape函数供querystring.stringify使用,必要时,可以重写。

querystring.unescape

unescape函数供querystring.parse使用。必要时,可以重写。

首先会尝试用decodeURIComponent,如果失败,会回退,不会抛出格式不正确的URLs。


Node.js Addons(插件)是动态链接的共享对象。他提供了C/C++类库能力。这些API比较复杂,他包以下几个类库:

  • V8 JavaScript, C++类库。用来和JavaScript交互,比如创建对象,调用函数等等。在v8.h头文件中 (目录地址deps/v8/include/v8.h),线上地址online

  • libuv,C事件循环库。等待文件描述符变为可读,等待定时器,等待信号时,会和libuv打交道。或者说,如果你需要和I/O打交道,就会用到libuv。

  • 内部Node类库。其中最重要的类node::ObjectWrap,你会经常派生自它。

  • 其他的参见deps/

Node已经将所有的依赖编译成可以执行文件,所以你不必担心这些类库的链接问题。

以下所有例子可以在download下载,也许你可以从中找一个作为你的扩展插件。

Hello world

现在我们来写一个C++插件的小例子,它的效果和以下JS代码一致:

module.exports.hello = function() { return 'world'; };

通过以下代码来创建hello.cc文件:

// hello.cc#include <node.h>using namespace v8;void Method(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world"));}void init(Handle<Object> exports) {  NODE_SET_METHOD(exports, "hello", Method);}NODE_MODULE(addon, init)

注意:所有的Node插件必须输出一个初始化函数:

void Initialize (Handle<Object> exports);NODE_MODULE(module_name, Initialize)

NODE_MODULE之后的代码没有分号,因为它不是一个函数 (参见node.h)。

module_name必须和二进制文件名字一致 (后缀是.node)。

源文件会编译成addon.node二进制插件。为此我们创建了一个很像JSON的binding.gyp文件,它包含配置信息,这个文件用node-gyp编译。

{  "targets": [    {      "target_name": "addon",      "sources": [ "hello.cc" ]    }  ]}

下一步创建一个node-gyp configure工程,在平台上生成这些文件。

创建后,在build/文件夹里拥有一个Makefile (Unix系统) 文件或者vcxproj文件(Windows 系统)。接着调用node-gyp build命令编译,生成.node文件。这些文件位于build/Release/目录里。

现在,你能在Node工程中使用这些二进制扩展插件,在hello.js中声明require之前编译的hello.node:

// hello.jsvar addon = require('./build/Release/addon');console.log(addon.hello()); // 'world'

更多的信息请参考https://github.com/arturadib/node-qt

插件模式

下面是一些addon插件的模式,帮助你开始编码。v8 reference文档里包含v8的各种接口,Embedder's Guide这个文档包含各种说明,比如handles, scopes, function templates等等。

在使用这些例子前,你需要先用node-gyp编译。创建binding.gyp 文件:

{  "targets": [    {      "target_name": "addon",      "sources": [ "addon.cc" ]    }  ]}

将文件名加入到sources数组里就可以使用多个.cc文件,例如 :

"sources": ["addon.cc", "myexample.cc"]

准备好binding.gyp文件后, 你就能配置并编译插件:

$ node-gyp configure build

函数参数

从以下模式中解释了如何从JavaScript函数中读取参数,并返回结果。仅需要一个addon.cc文件:

// addon.cc#include <node.h>using namespace v8;void Add(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  if (args.Length() < 2) {    isolate->ThrowException(Exception::TypeError(        String::NewFromUtf8(isolate, "Wrong number of arguments")));    return;  }  if (!args[0]->IsNumber() || !args[1]->IsNumber()) {    isolate->ThrowException(Exception::TypeError(        String::NewFromUtf8(isolate, "Wrong arguments")));    return;  }  double value = args[0]->NumberValue() + args[1]->NumberValue();  Local<Number> num = Number::New(isolate, value);  args.GetReturnValue().Set(num);}void Init(Handle<Object> exports) {  NODE_SET_METHOD(exports, "add", Add);}NODE_MODULE(addon, Init)

可以用以下的JavaScript代码片段测试:

// test.jsvar addon = require('./build/Release/addon');console.log( 'This should be eight:', addon.add(3,5) );

回调Callbacks

你也能传JavaScript函数给C++函数,并执行它。在addon.cc中:

// addon.cc#include <node.h>using namespace v8;void RunCallback(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  Local<Function> cb = Local<Function>::Cast(args[0]);  const unsigned argc = 1;  Local<Value> argv[argc] = { String::NewFromUtf8(isolate, "hello world") };  cb->Call(isolate->GetCurrentContext()->Global(), argc, argv);}void Init(Handle<Object> exports, Handle<Object> module) {  NODE_SET_METHOD(module, "exports", RunCallback);}NODE_MODULE(addon, Init)

注意,这个例子中使用了Init()里的2个参数,module对象是第二个参数。它允许addon使用一个函数完全重写exports

可以用以下的代码来测试:

// test.jsvar addon = require('./build/Release/addon');addon(function(msg){  console.log(msg); // 'hello world'});

对象工厂

addon.cc模式里,你能用C++函数创建并返回一个新的对象,这个对象所包含的msg属性是由createObject()函数传入:

// addon.cc#include <node.h>using namespace v8;void CreateObject(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  Local<Object> obj = Object::New(isolate);  obj->Set(String::NewFromUtf8(isolate, "msg"), args[0]->ToString());  args.GetReturnValue().Set(obj);}void Init(Handle<Object> exports, Handle<Object> module) {  NODE_SET_METHOD(module, "exports", CreateObject);}NODE_MODULE(addon, Init)

使用JavaScript测试:

// test.jsvar addon = require('./build/Release/addon');var obj1 = addon('hello');var obj2 = addon('world');console.log(obj1.msg+' '+obj2.msg); // 'hello world'

工厂模式

这个模式里展示了如何创建并返回一个JavaScript函数,它是由C++函数包装的 :

// addon.cc#include <node.h>using namespace v8;void MyFunction(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  args.GetReturnValue().Set(String::NewFromUtf8(isolate, "hello world"));}void CreateFunction(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, MyFunction);  Local<Function> fn = tpl->GetFunction();  // omit this to make it anonymous  fn->SetName(String::NewFromUtf8(isolate, "theFunction"));  args.GetReturnValue().Set(fn);}void Init(Handle<Object> exports, Handle<Object> module) {  NODE_SET_METHOD(module, "exports", CreateFunction);}NODE_MODULE(addon, Init)

测试:

// test.jsvar addon = require('./build/Release/addon');var fn = addon();console.log(fn()); // 'hello world'

包装C++对象

以下会创建一个C++对象的包装MyObject,这样他就能在JavaScript中用new实例化。首先在addon.cc中准备主要模块:

// addon.cc#include <node.h>#include "myobject.h"using namespace v8;void InitAll(Handle<Object> exports) {  MyObject::Init(exports);}NODE_MODULE(addon, InitAll)

接着在myobject.h创建包装,它继承自node::ObjectWrap:

// myobject.h#ifndef MYOBJECT_H#define MYOBJECT_H#include <node.h>#include <node_object_wrap.h>class MyObject : public node::ObjectWrap { public:  static void Init(v8::Handle<v8::Object> exports); private:  explicit MyObject(double value = 0);  ~MyObject();  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);  static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args);  static v8::Persistent<v8::Function> constructor;  double value_;};#endif

myobject.cc中实现各种暴露的方法,通过给构造函数添加prototype属性来暴露plusOne方法:

// myobject.cc#include "myobject.h"using namespace v8;Persistent<Function> MyObject::constructor;MyObject::MyObject(double value) : value_(value) {}MyObject::~MyObject() {}void MyObject::Init(Handle<Object> exports) {  Isolate* isolate = Isolate::GetCurrent();  // Prepare constructor template  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);  tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject"));  tpl->InstanceTemplate()->SetInternalFieldCount(1);  // Prototype  NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);  constructor.Reset(isolate, tpl->GetFunction());  exports->Set(String::NewFromUtf8(isolate, "MyObject"),               tpl->GetFunction());}void MyObject::New(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  if (args.IsConstructCall()) {    // Invoked as constructor: `new MyObject(...)`    double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();    MyObject* obj = new MyObject(value);    obj->Wrap(args.This());    args.GetReturnValue().Set(args.This());  } else {    // Invoked as plain function `MyObject(...)`, turn into construct call.    const int argc = 1;    Local<Value> argv[argc] = { args[0] };    Local<Function> cons = Local<Function>::New(isolate, constructor);    args.GetReturnValue().Set(cons->NewInstance(argc, argv));  }}void MyObject::PlusOne(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder());  obj->value_ += 1;  args.GetReturnValue().Set(Number::New(isolate, obj->value_));}

测试:

// test.jsvar addon = require('./build/Release/addon');var obj = new addon.MyObject(10);console.log( obj.plusOne() ); // 11console.log( obj.plusOne() ); // 12console.log( obj.plusOne() ); // 13

包装对象工厂

当你想创建本地对象,又不想在JavaScript中严格的使用new初始化的时候,以下方法非常实用:

var obj = addon.createObject();// instead of:// var obj = new addon.Object();

addon.cc中注册createObject方法:

// addon.cc#include <node.h>#include "myobject.h"using namespace v8;void CreateObject(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  MyObject::NewInstance(args);}void InitAll(Handle<Object> exports, Handle<Object> module) {  MyObject::Init();  NODE_SET_METHOD(module, "exports", CreateObject);}NODE_MODULE(addon, InitAll)

myobject.h中有静态方法NewInstance,他能实例化对象(它就像JavaScript的new):

// myobject.h#ifndef MYOBJECT_H#define MYOBJECT_H#include <node.h>#include <node_object_wrap.h>class MyObject : public node::ObjectWrap { public:  static void Init();  static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args); private:  explicit MyObject(double value = 0);  ~MyObject();  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);  static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args);  static v8::Persistent<v8::Function> constructor;  double value_;};#endif

这个实现方法和myobject.cc类似:

// myobject.cc#include <node.h>#include "myobject.h"using namespace v8;Persistent<Function> MyObject::constructor;MyObject::MyObject(double value) : value_(value) {}MyObject::~MyObject() {}void MyObject::Init() {  Isolate* isolate = Isolate::GetCurrent();  // Prepare constructor template  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);  tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject"));  tpl->InstanceTemplate()->SetInternalFieldCount(1);  // Prototype  NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);  constructor.Reset(isolate, tpl->GetFunction());}void MyObject::New(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  if (args.IsConstructCall()) {    // Invoked as constructor: `new MyObject(...)`    double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();    MyObject* obj = new MyObject(value);    obj->Wrap(args.This());    args.GetReturnValue().Set(args.This());  } else {    // Invoked as plain function `MyObject(...)`, turn into construct call.    const int argc = 1;    Local<Value> argv[argc] = { args[0] };    Local<Function> cons = Local<Function>::New(isolate, constructor);    args.GetReturnValue().Set(cons->NewInstance(argc, argv));  }}void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  const unsigned argc = 1;  Handle<Value> argv[argc] = { args[0] };  Local<Function> cons = Local<Function>::New(isolate, constructor);  Local<Object> instance = cons->NewInstance(argc, argv);  args.GetReturnValue().Set(instance);}void MyObject::PlusOne(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder());  obj->value_ += 1;  args.GetReturnValue().Set(Number::New(isolate, obj->value_));}

测试:

// test.jsvar createObject = require('./build/Release/addon');var obj = createObject(10);console.log( obj.plusOne() ); // 11console.log( obj.plusOne() ); // 12console.log( obj.plusOne() ); // 13var obj2 = createObject(20);console.log( obj2.plusOne() ); // 21console.log( obj2.plusOne() ); // 22console.log( obj2.plusOne() ); // 23

传递包装对象

除了包装并返回C++对象,你可以使用Node的node::ObjectWrap::Unwrap帮助函数来解包。在下面的addon.cc中,我们介绍了一个add()函数,它能获取2个 MyObject对象:

// addon.cc#include <node.h>#include <node_object_wrap.h>#include "myobject.h"using namespace v8;void CreateObject(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  MyObject::NewInstance(args);}void Add(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  MyObject* obj1 = node::ObjectWrap::Unwrap<MyObject>(      args[0]->ToObject());  MyObject* obj2 = node::ObjectWrap::Unwrap<MyObject>(      args[1]->ToObject());  double sum = obj1->value() + obj2->value();  args.GetReturnValue().Set(Number::New(isolate, sum));}void InitAll(Handle<Object> exports) {  MyObject::Init();  NODE_SET_METHOD(exports, "createObject", CreateObject);  NODE_SET_METHOD(exports, "add", Add);}NODE_MODULE(addon, InitAll)

介绍myobject.h里的一个公开方法,它能在解包后使用私有变量:

// myobject.h#ifndef MYOBJECT_H#define MYOBJECT_H#include <node.h>#include <node_object_wrap.h>class MyObject : public node::ObjectWrap { public:  static void Init();  static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);  inline double value() const { return value_; } private:  explicit MyObject(double value = 0);  ~MyObject();  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);  static v8::Persistent<v8::Function> constructor;  double value_;};#endif

myobject.cc的实现方法和之前的类似:

// myobject.cc#include <node.h>#include "myobject.h"using namespace v8;Persistent<Function> MyObject::constructor;MyObject::MyObject(double value) : value_(value) {}MyObject::~MyObject() {}void MyObject::Init() {  Isolate* isolate = Isolate::GetCurrent();  // Prepare constructor template  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);  tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject"));  tpl->InstanceTemplate()->SetInternalFieldCount(1);  constructor.Reset(isolate, tpl->GetFunction());}void MyObject::New(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  if (args.IsConstructCall()) {    // Invoked as constructor: `new MyObject(...)`    double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();    MyObject* obj = new MyObject(value);    obj->Wrap(args.This());    args.GetReturnValue().Set(args.This());  } else {    // Invoked as plain function `MyObject(...)`, turn into construct call.    const int argc = 1;    Local<Value> argv[argc] = { args[0] };    Local<Function> cons = Local<Function>::New(isolate, constructor);    args.GetReturnValue().Set(cons->NewInstance(argc, argv));  }}void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) {  Isolate* isolate = Isolate::GetCurrent();  HandleScope scope(isolate);  const unsigned argc = 1;  Handle<Value> argv[argc] = { args[0] };  Local<Function> cons = Local<Function>::New(isolate, constructor);  Local<Object> instance = cons->NewInstance(argc, argv);  args.GetReturnValue().Set(instance);}

测试:

// test.jsvar addon = require('./build/Release/addon');var obj1 = addon.createObject(10);var obj2 = addon.createObject(20);var result = addon.add(obj1, obj2);console.log(result); // 30

相关教程

如果你想了解更多与C++相关的知识,则可以参考本站的《C++教程》


本节介绍Node.js readline(逐行读取)模块,它用于提供一个接口。

稳定性: 2 - 不稳定

通过 require('readline'),你可以使用这个模块。逐行读取(Readline)可以逐行读取流(比如process.stdin)。

访问该模块的方法如下:

const readline = require('readline');

一旦你开启了这个模块,node程序将不会终止,直到你关闭接口。以下的代码展示了如何优雅的退出程序:

var readline = require('readline');var rl = readline.createInterface({  input: process.stdin,  output: process.stdout});rl.question("What do you think of node.js? ", function(answer) {  // TODO: Log the answer in a database  console.log("Thank you for your valuable feedback:", answer);  rl.close();});

readline.createInterface(options)

创建一个逐行读取(Readline)Interface实例。参数"options"对象有以下值:

  • input- 监听的可读流 (必填)。

  • output- 逐行读取(Readline)数据要写入的可写流(可选)。

  • completer- 用于Tab自动补全的可选函数。参见下面的例子。

  • terminal- 如果希望和TTY一样,对待inputoutput流,设置为true。并且由ANSI/VT100转码。默认情况下,检查isTTY是否在output流上实例化。

completer给出当前行的入口,应该返回包含2条记录的数组。

  1. 一个匹配当前输入补全的字符串数组

  2. 用来匹配的子字符串

最终像这样:[[substr1, substr2, ...], originalsubstring].

例子:

function completer(line) {  var completions = '.help .error .exit .quit .q'.split(' ')  var hits = completions.filter(function(c) { return c.indexOf(line) == 0 })  // show all completions if none found  return [hits.length ? hits : completions, line]}

同时,completer可以异步运行,此时接收到2个参数:

function completer(linePartial, callback) {  callback(null, [['123'], linePartial]);}

为了接受用户输入,createInterface通常和process.stdinprocess.stdout一起使用:

var readline = require('readline');var rl = readline.createInterface({  input: process.stdin,  output: process.stdout});

如果你有逐行读取(Readline)实例, 通常会监听"line"事件.

如果这个实例参数terminal = true,而且定义了output.columns属性,那么output流将会最佳兼容性,并且,当columns变化时(当它是TTY时,process.stdout会自动这么做),会在output流上触发"resize"事件。

Class: Interface

代表一个包含输入/输出流的逐行读取(Readline)接口的类。

rl.setPrompt(prompt)

设置提示符,比如当你再命令行里运行node时,可以看到node的提示符>

rl.prompt([preserveCursor])

为用户输入准备好逐行读取(Readline),将当前setPrompt选项方法哦新的行中,让用户有新的地方输入。设置preserveCursortrue,防止当前的游标重置为0

如果暂停,使用createInterface也可以重置input输入流。

调用createInterface时,如果output设置为nullundefined,不会重新写提示符。

rl.question(query, callback)

预先提示query,用户应答后触发callback。给用户显示query后,用户应答被输入后,调用callback

如果暂停,使用createInterface也可以重置input输入流。

调用createInterface时,如果output设置为nullundefined,不会重新写提示符。

例子:

interface.question('What is your favorite food?', function(answer) {  console.log('Oh, so your favorite food is ' + answer);});

rl.pause()

暂停逐行读取(Readline)的input输入流, 如果需要可以重新启动。

注意,这不会立即暂停流。调用pause后还会有很多事件触发,包含line

rl.resume()

恢复 逐行读取(Readline)input输入流.

rl.close()

关闭Interface实例, 放弃控制输入输出流。会触发"close"事件。

rl.write(data[, key])

调用createInterface后,将数据data写到output输出流,除非outputnull,或未定义undefinedkey是一个代表键序列的对象;当终端是一个 TTY 时可用。

暂停input输入流后,这个方法可以恢复。

例子:

rl.write('Delete me!');// Simulate ctrl+u to delete the line written previouslyrl.write(null, {ctrl: true, name: 'u'});

Events

事件: 'line'

function (line) {}

input输入流收到 后触发,通常因为用户敲回车或返回键。这是监听用户输入的好办法。

监听line的例子:

rl.on('line', function (cmd) {  console.log('You just typed: '+cmd);});

事件: 'pause'

function () {}

暂停input输入流后,会触发这个方法。

当输入流未被暂停,但收到SIGCONT也会触发。 (详见SIGTSTPSIGCONT事件)

监听pause的例子:

rl.on('pause', function() {  console.log('Readline paused.');});

事件: 'resume'

function () {}

恢复input输入流后,会触发这个方法。

监听resume的例子:

rl.on('resume', function() {  console.log('Readline resumed.');});

事件: 'close'

function () {}

调用close()方法时会触发。

input输入流收到"end"事件时会触发。一旦触发,可以认为Interface实例结束。例如当input输入流收到^D,被当做EOT

如果没有SIGINT事件监听器,当input 输入流接收到^C(被当做SIGINT),也会触发这个事件。

事件: 'SIGINT'

function () {}

input输入流收到^C时会触发, 被当做SIGINT。如果没有SIGINT 事件监听器,当input 输入流接收到SIGINT(被当做SIGINT),会触发 pause事件。

监听SIGINT的例子:

rl.on('SIGINT', function() {  rl.question('Are you sure you want to exit?', function(answer) {    if (answer.match(/^y(es)?$/i)) rl.pause();  });});

事件: 'SIGTSTP'

function () {}

Windows 里不可用

input输入流收到^Z时会触发,被当做SIGTSTP。如果没有SIGINT事件监听器,当input 输入流接收到SIGTSTP,程序将会切换到后台。

当程序通过fg恢复,将会触发pauseSIGCONT事件。你可以使用两者中任一事件来恢复流。

程切换到后台前,如果暂停了流,pauseSIGCONT事件不会被触发。

监听SIGTSTP的例子:

rl.on('SIGTSTP', function() {  // This will override SIGTSTP and prevent the program from going to the  // background.  console.log('Caught SIGTSTP.');});

事件: 'SIGCONT'

function () {}

Windows里不可用

一旦input流中含有^Z并被切换到后台就会触发。被当做SIGTSTP,然后继续执行fg(1)。程切换到后台前,如果流没被暂停,这个事件可以被触发。

监听SIGCONT的例子:

rl.on('SIGCONT', function() {  // `prompt` will automatically resume the stream  rl.prompt();});

例子: Tiny CLI

以下的例子,展示了如何所有这些方法的命令行接口:

var readline = require('readline'),    rl = readline.createInterface(process.stdin, process.stdout);rl.setPrompt('OHAI> ');rl.prompt();rl.on('line', function(line) {  switch(line.trim()) {    case 'hello':      console.log('world!');      break;    default:      console.log('Say what? I might have heard `' + line.trim() + '`');      break;  }  rl.prompt();}).on('close', function() {  console.log('Have a great day!');  process.exit(0);});

readline.cursorTo(stream, x, y)

在TTY流里,移动光标到指定位置。

readline.moveCursor(stream, dx, dy)

在TTY流里,移动光标到当前位置的相对位置。

readline.clearLine(stream, dir)

清空TTY流里指定方向的行。dir是以下值:

  • -1- 从光标到左边
  • 1- 从光标到右边
  • 0- 整行

readline.clearScreenDown(stream)

清空屏幕上从当前光标位置起的内容。


在Node.js中我们可以直接访问到全局对象。

这些对象在所有模块里都是可用的,有些对象不是在全局作用域而是在模块作用域里,这些情况将在本文的内容中进行介绍。

global

  • {Object} 全局命名空间对象。

在浏览器中,全局作用域就是顶级域。如果在全局域内定义变量var something将会是全局变量。而在Node中,顶级域并不是全局域;在模块里定义变量 var something只是模块内可用。

process

  • {Object}

进程对象。参见process object章节.

console

  • {Object}

用来打印stdout和stderr。参见console章节.

Class: Buffer

  • {Function}

用来处理二进制数据。参见buffer 章节

require()

  • {Function}

引入模块。参见Modules章节。require实际上并非全局的,而是各个本地模块有效。

require.resolve()

使用内部require()机制来查找module位置,但是不加载模块,只是返回解析过的文件名。

require.cache

  • {Object}

引入模块时会缓存到这个对象。通过删除该对象键值,下次调用require将会重载该模块。

require.extensions

稳定性: 0 - 抛弃
  • {Object}

指导require如何处理特定的文件扩展名。

.sjs文件当作.js文件处理:

require.extensions['.sjs'] = require.extensions['.js'];

抛弃 以前这个列表用来加载按需编译的非JavaScript模块到node。实际上,有更好的办法来解决这个问题,比如通过其他node程序来加载模块,或者提前编译成 JavaScript。

由于模块系统已经锁定,该功能可能永远不会去掉。改动它可能会产生bug,所以最好不要动它。

__filename

  • {String}

被执行的代码的文件名是相对路径。对于主程序来说,这和命令行里未必用同一个文件名。模块里的值是模块文件的路径。

列如,运行/Users/mjr里的node example.js

console.log(__filename);// /Users/mjr/example.js

__filename不是全局的,而是模块本地的。

__dirname

  • {String}

执行的script代码所在的文件夹的名字。

列如,运行/Users/mjr里的node example.js

console.log(__dirname);// /Users/mjr

__dirname不是全局的,而是模块本地的。

module

  • {Object}

当前模块的引用。通过require()module.exports定义了哪个模块输出可用。

module不是全局的,而是模块本地的。

更多信息参见module system documentation

exports

module.exports的引用。关于什么时候使用exportsmodule.exports,可以参考module system documentation

module不是全局的,而是模块本地的。

更多信息参见module system documentation

更多信息参见module 章节

setTimeout(cb, ms)

最少在ms毫秒后调回调函数。实际的延迟依赖于外部因素,比如操作系统的粒度和负载。

timeout值有效范围为1-2,147,483,647。如果超过该范围,将会变为1毫秒。通常,定时器不应该超过24.8天。

返回一个代表定时器的句柄值。

clearTimeout(t)

停止一个之前通过setTimeout()创建的定时器。不会再被执行回调。

setInterval(cb, ms)

每隔ms毫秒调用回调函数cb。实际的间隔依赖于外部因素,比如操作系统的粒度和系统负载。通常会大于ms

间隔值的有效范围在1-2,147,483,647。如果超过该范围,将会变为1毫秒。通常,定时器不应该超过24.8天。

返回一个代表该定时器的句柄值。

clearInterval(t)

停止一个之前通过setInterval()创建的定时器。不会再被执行回调。

timer函数是全局变量。参见timers章节。


Node.js是基于单线程模型架构的,它能够拥有高效的CPU利用率,却限制了多个核心CPU的使用,为此,Node.js提供了child_process 模块以通过多线程来实现对多核CPU的使用。

稳定性: 3 - 稳定

Node通过child_process模块提供了popen(3)数据流。

它能在非阻塞的方式中,通过stdin,stdoutstderr传递数据。 

请注意:某些程序使用内部线性缓冲I/O, 它并不妨碍node.js,只是你发送给子进程的数据不会被立即取消。

你可以使用require('child_process').spawn()require('child_process').fork()创建一个子进程。这两种方法有区别,在下文中将进行解释。

开发过程中查看synchronous counterparts效率会更高。

类: ChildProcess

ChildProcess是一个EventEmitter

子进程有三个相关的流child.stdin,child.stdoutchild.stderr。他们可能和会父进程的stdiostreams共享,也可作为独立的对象。

不能直接调用ChildProcess类,使用spawn(),exec(),execFile()fork()方法来创建子进程的实例。

事件: 'error'

  • err {Error Object}错误。

发生于:

  1. 无法创建进程。
  2. 无法杀死进程。
  3. 无论什么原因导致给子进程发送消息失败。

注意:exit事件有可能在错误发生后调用,也可能不调用,所以如果你监听这两个事件来触发函数,记得预防函数会被调用2次。

参考ChildProcess#kill()ChildProcess#send()

事件: 'exit'

  • code {Number} 退出代码, 正常退出时才有效。
  • signal {String} 如果是被父进程杀死,则它为传递给子进程的信号

子进程结束的时候触发这个事件。如果子进程正常终止,则code为最终的退出代码,否则为null。如果是由signal引起的终止,则signal为字符串,否则为 null

注意:子进程的stdio流可能仍为开启模式。

注意,node为'SIGINT''SIGTERM' 建立句柄,所以当信号来临的时候,他们不会终止而是退出。

参靠waitpid(2)

事件: 'close'

  • code{Number} 退出代码, 正常退出时才有效。
  • signal{String} 如果是被父进程杀死,则它为传递给子进程的信号。

子进程里所有stdio流都关闭时触发这个事件。要和'exit'区分开,因为多进程可以共享一个stdio流。

Event: 'disconnect'

父进程或子进程中调用.disconnect()方法后触发这个事件。断开后不会在互发消息,并且.connected属性值为false。

Event: 'message'

  • message{Object} 一个解析过的JSON对象,或者一个原始值。
  • sendHandle{Handle object} 一个Socket或Server对象

通过.send(message, [sendHandle])传递消息。

child.stdin

  • {Stream object}

子进程的stdinWritable Stream(可写流)。如果子进程在等待输入,它就会暂停直到通过调用end()来关闭。

child.stdinchild.stdio[0]的缩写。这两个都指向同一个对象,或者null。

child.stdout

  • {Stream object}

子进程的stdoutReadable Stream(可读流)。

child.stdoutchild.stdio[1]的缩写。 这两个都指向同一个对象,或者null。

child.stderr

  • {Stream object}

子进程的stderrReadable Stream(可写流)。

child.stderrchild.stdio[2]缩写。这两个都指向同一个对象,或者null。

child.stdio

  • {Array}

子进程的管道数组和spawnstdio里设置为'pipe' 的内容次序相对应。

注意,流[0-2]也能分别用ChildProcess.stdin、ChildProcess.stdout和ChildProcess.stderrNote来表示。

在下面的例子里,只有子进程的fd1设置为pipe管道,所以父进程的child.stdio[1]是流(stream),数组里其他值为null

child = child_process.spawn("ls", {    stdio: [      0, // use parents stdin for child      'pipe', // pipe child's stdout to parent      fs.openSync("err.out", "w") // direct child's stderr to a file    ]});assert.equal(child.stdio[0], null);assert.equal(child.stdio[0], child.stdin);assert(child.stdout);assert.equal(child.stdio[1], child.stdout);assert.equal(child.stdio[2], null);assert.equal(child.stdio[2], child.stderr);

child.pid

  • {Integer}

子进程的PID。

例子:

var spawn = require('child_process').spawn,    grep  = spawn('grep', ['ssh']);console.log('Spawned child pid: ' + grep.pid);grep.stdin.end();

child.connected

  • {Boolean} 调用`.disconnect'后设置为false

如果.connected为 false,消息不再可用。

child.kill([signal])

  • signal{String}

发送信号给子进程。如果没有参数,会发送'SIGTERM',参见signal(7)里的可用的信号列表。

var spawn = require('child_process').spawn,    grep  = spawn('grep', ['ssh']);grep.on('close', function (code, signal) {  console.log('child process terminated due to receipt of signal '+signal);});// send SIGHUP to processgrep.kill('SIGHUP');

当信号无法传递的时候会触发'error'事件。给已经终止的进程发送信号不会触发'error'事件,但是可以能引起不可预知的后果: 因为有可能PID (进程ID) 已经重新分配给其他进程,信号就会被发送到新的进程里,无法想象这样会引发什么样的事情。

注意:当函数调用kill信号的时候,它实际并并不会杀死进程,只是发送信号给进程。

参见kill(2)

child.send(message[, sendHandle])

  • message{Object}
  • sendHandle{Handle object}

使用child_process.fork()的时候,你能用child.send(message, [sendHandle])给子进程写数据,子进程通过'message'接收消息。

例如:

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' :

process.on('message', function(m) {  console.log('CHILD got message:', m);});process.send({ foo: 'bar' });

子进程代码里的process对象拥有send()方法,当它通过信道接收到信息时会触发,并返回对象。

注意:父进程和子进程send()是同步的,不要用来发送大块的数据(可以用管道来代替,参见child_process.spawn)。

不过发送{cmd: 'NODE_foo'}消息是特殊情况。所有包含NODE_前缀的消息都不会被触发,因为它们是node的内部的核心消息,它们会在internalMessage事件里触发,尽量避免使用这个特性。

child.send()里的sendHandle属性用来发送TCP服务或socket对象给其他的进程,子进程会用接收到的对象作为message事件的第二个参数。

如果不能发出消息会触发'error'事件,比如子进程已经退出。

例子: 发送 server 对象

以下是例子:

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');});server.listen(1337, function() {  child.send('server', server);});

子进程将会收到这个server对象:

process.on('message', function(m, server) {  if (m === 'server') {    server.on('connection', function (socket) {      socket.end('handled by child');    });  }});

注意,现在父子进程共享了server,某些连接会被父进程处理,某些会被子进程处理。

dgram服务器,工作流程是一样的,监听的是message事件,而不是connection,使用server.bind而不是server.listen。(目前仅支持UNIX平台)

例子: 发送 socket 对象

以下是发送socket对象的例子。他将会创建2个子线程,并且同时处理连接,一个将远程地址74.125.127.100当做VIP发送到一个特殊的子进程,另外一个发送到正常进程。var normal=require('child_process').fork('child.js', ['normal']);var special = require('child_process').fork('child.js', ['special']);

// Open up the server and send sockets to childvar server = require('net').createServer();server.on('connection', function (socket) {  // if this is a VIP  if (socket.remoteAddress === '74.125.127.100') {    special.send('socket', socket);    return;  }  // just the usual dudes  normal.send('socket', socket);});server.listen(1337);

child.js代码如下:

process.on('message', function(m, socket) {  if (m === 'socket') {    socket.end('You were handled as a ' + process.argv[2] + ' person');  }});

注意,当socket发送给子进程后,如果这个socket被销毁,父进程不再跟踪它,相应的.connections属性会变为null。这种情况下,不建议使用 .maxConnections

child.disconnect()

关闭父子进程间的所有IPC通道,能让子进程优雅的退出。调用这个方法后,父子进程里的.connected标志会变为false,之后不能再发送消息。

当进程里没有消息需要处理的时候,会触发'disconnect'事件。

注意,在子进程还有IPC通道的情况下(如fork()),也可以调用process.disconnect()来关闭它。

创建异步处理

这些方法遵从常用的异步处理模式(比如回调,或者返回一个事件处理)。

child_process.spawn(command[, args][, options])

  • command {String} 要运行的命令
  • args {Array} 字符串参数表
  • options {Object}
    • cwd {String} 子进程的工作目录
    • env {Object} 环境
    • stdio {Array|String} 子进程的stdio配置。
    • customFds {Array} Deprecated 作为子进程stdio使用的文件标示符。
    • detached {Boolean} 子进程将会变成一个进程组的领导者。
    • uid {Number} 设置用户进程的ID。(参见setuid(2))
    • gid {Number} 设置进程组的ID。(参见 setgid(2))
  • 返回: {ChildProcess object}

用指定的command发布一个子进程,args是命令行参数。如果忽略,args是空数组。

第三个参数用来指定附加设置,默认值:

{ cwd: undefined,  env: process.env}

创建的子进程里使用cwd指定工作目录,如果没有指定,默认继承自当前的工作目录。

使用env来指定新进程可见的环境变量。默认是process.env

例如,运行ls -lh /usr,获取stdout,stderr和退出代码:

var spawn = require('child_process').spawn,    ls    = spawn('ls', ['-lh', '/usr']);ls.stdout.on('data', function (data) {  console.log('stdout: ' + data);});ls.stderr.on('data', function (data) {  console.log('stderr: ' + data);});ls.on('close', function (code) {  console.log('child process exited with code ' + code);});

例如:通过一个非常精巧的方法执行'ps ax | grep ssh'

var spawn = require('child_process').spawn,    ps    = spawn('ps', ['ax']),    grep  = spawn('grep', ['ssh']);ps.stdout.on('data', function (data) {  grep.stdin.write(data);});ps.stderr.on('data', function (data) {  console.log('ps stderr: ' + data);});ps.on('close', function (code) {  if (code !== 0) {    console.log('ps process exited with code ' + code);  }  grep.stdin.end();});grep.stdout.on('data', function (data) {  console.log('' + data);});grep.stderr.on('data', function (data) {  console.log('grep stderr: ' + data);});grep.on('close', function (code) {  if (code !== 0) {    console.log('grep process exited with code ' + code);  }});

options.stdio

stdio可能是以下几个参数之一:

  • 'pipe'-['pipe', 'pipe', 'pipe'],默认值
  • 'ignore'-['ignore', 'ignore', 'ignore']
  • 'inherit'-[process.stdin, process.stdout, process.stderr][0,1,2]

child_process.spawn()里的'stdio'参数是一个数组,它和子进程的fd相对应,它的值如下:

  1. 'pipe'- 创建在父进程和子进程间的pipe。管道的父进程端以child_process的属性形式暴露给父进程,例如ChildProcess.stdio[fd]。为fds 0 - 2创建的管道也可以通过ChildProcess.stdin,ChildProcess.stdout和ChildProcess.stderr来独立的访问。

  2. 'ipc'- 在父进程和子进程间创建一个IPC通道来传递消息/文件描述符。一个子进程最多有1个IPC stdio文件标识。设置这个选项会激活ChildProcess.send() 方法。如果子进程向此文件标识写入JSON消息,则会触发ChildProcess.on('message') 。如果子进程是Node.js程序,那么IPC通道会激活process.send()和 process.on('message')。

  3. 'ignore'- 在子进程里不要设置这个文件标识,需要注意,Node总会为其spawn的进程打开fd 0-2。如果任何一个被ignored,node将会打开/dev/null并赋给子进程的fd。

  4. Stream对象 - 共享一个tty、file、socket或刷(pipe)可读或可写流给子进程。该流底层(underlying)的文件标识在子进程中被复制给stdio数组索引对应的文件标识(fd)。

  5. 正数 - 这个整数被理解为一个在父进程中打开的文件标识,它和子进程共享,就和共享Stream对象类似。

  6. null,undefined- 使用默认值。 对于stdio fds 0、1 and 2 (换句话说,stdin、stdout或者stderr) ,pipe管道被建立。 对于fd 3及之后,默认是 'ignore'

例如:

var spawn = require('child_process').spawn;// Child will use parent's stdiosspawn('prg', [], { stdio: 'inherit' });// Spawn child sharing only stderrspawn('prg', [], { stdio: ['pipe', 'pipe', process.stderr] });// Open an extra fd=4, to interact with programs present a// startd-style interface.spawn('prg', [], { stdio: ['pipe', null, null, null, 'pipe'] });

options.detached

如果设置了detached选项,子进程将会被作为新进程组的leader,这使得子进程可以在父进程退出后继续运行。

缺省情况下父进程会等detached的子进程退出。要阻止父进程等待一个这样的子进程,调用child.unref()方法,则父进程的事件循环引用计数中将不会包含这个子进程。

detaching一个长期运行的进程,并重新将输出指向文件:

 var fs = require('fs'),     spawn = require('child_process').spawn,     out = fs.openSync('./out.log', 'a'),     err = fs.openSync('./out.log', 'a'); var child = spawn('prg', [], {   detached: true,   stdio: [ 'ignore', out, err ] }); child.unref();

使用detached选项来启动一个长时间运行的进程时,进程不会在后台保持运行,除非他提供了一个不连接到父进程的stdio。如果继承了父进程的stdio,则子进程会继续控制终端。

options.customFds

已废弃,customFds允许指定特定文件描述符作为子进程的stdio。该API无法移植到所有平台,因此被废弃。使用customFds可以将新进程的 [stdin, stdout,stderr] 钩到已有流上;-1表示创建新流。自己承担使用风险。

参见:child_process.exec()child_process.fork()

child_process.exec(command[, options], callback)

  • command {String} 要执行的命令,空格分割
  • options {Object}
    • cwd {String} 子进程的当前工作目录
    • env {Object} 环境变量
    • encoding {String} (默认: 'utf8')
    • shell {String} 运行命令的shell(默认为: '/bin/sh' UNIX, 'cmd.exe' Windows, 该shell必须接收UNIX上的-c开关 ,或者Windows上的/s /c开关。Windows上,命令解析必须兼容cmd.exe。)
    • timeout {Number} (默认: 0)
    • maxBuffer {Number} (默认: 200*1024)
    • killSignal {String} (默认: 'SIGTERM')
    • uid {Number} 设置进程里的用户标识。 (见 setuid(2)。)
    • gid {Number} 设置进程里的群组标识。(见 setgid(2)。)
  • callback {Function} 进程终止的时候调用
    • error {Error}
    • stdout {Buffer}
    • stderr {Buffer}
  • 返回: ChildProcess对象

在shell里执行命令,并缓冲输出。

var exec = require('child_process').exec,    child;child = exec('cat *.js bad_file | wc -l',  function (error, stdout, stderr) {    console.log('stdout: ' + stdout);    console.log('stderr: ' + stderr);    if (error !== null) {      console.log('exec error: ' + error);    }});

回调参数是(error, stdout, stderr)。如果成功,则,error值为null。 如果失败,则error变为Error的实例,error.code等于子进程退出码,并且 error.signal会被设置为结束进程的信号名。

第二个参数可以设置一些选项。默认如下:

{ encoding: 'utf8',  timeout: 0,  maxBuffer: 200*1024,  killSignal: 'SIGTERM',  cwd: null,  env: null }

如果timeout大于0,子进程运行时间超过timeout时会被终止。killSignal(默认: 'SIGTERM')能杀死子进程。maxBuffer设定了stdout或stderr的最大数据量,如果子进程的数量量超过了,将会被杀死。

(file[, args][, options][, callback])

  • file {String} 要运行的程序的文件名
  • args {Array} 参数列表
  • options {Object}
    • cwd {String} 子进程的工作目录
    • env {Object} 环境
    • encoding {String} (默认: 'utf8')
    • timeout {Number} (默认: 0)
    • maxBuffer {Number} (默认: 200*1024)
    • killSignal {String} (默认: 'SIGTERM')
    • uid {Number} 设置进程里的用户标识。 (参见setuid(2)。)
    • gid {Number} 设置进程里的群组标识。(参见setgid(2)。)
  • callback {Function} 进程终止的时候调用
    • error {Error}
    • stdout {Buffer}
    • stderr {Buffer}
  • 返回: ChildProcess对象

child_process.exec()类似,不同之处在于这是执行一个指定的文件,因此它比child_process.exec精简些,参数相同。

child_process.fork(modulePath[, args][, options])

  • modulePath {String} 子进程里运行的模块
  • args {Array} 参数列表
  • options {Object}
    • cwd {String} 子进程的工作目录
    • env {Object} 环境
    • execPath {String} 执行文件路径
    • execArgv {Array} 执行参数(默认: process.execArgv)
    • silent {Boolean} 如果是 true ,子进程将会用父进程的 stdin, stdout, and stderr ,否则,将会继承自父进程, 更多细节,参见 spawn()stdio 参数里的 "pipe" 和 "inherit" 选项(默认 false)
    • uid {Number} 设置进程里的用户标识。 (见 setuid(2)。)
    • gid {Number} 设置进程里的群组标识。 (见 setgid(2)。)
  • 返回: ChildProcess对象

这是spawn()的特殊例子,用于派生Node进程。除了拥有子进程的所有方法,它的返回对象还拥有内置通讯通道。参见child.send(message, [sendHandle])

这些Nodes是全新的V8实例化,假设每个Node最少需要30ms的启动时间,10mb的存储空间,可想而知,创建几千个Node是不太现实的。

options对象中的execPath属性可以用于执行文件(非当前node )创建子进程。这需要小心使用,缺省情况下fd表示子进程的NODE_CHANNEL_FD环境变量。该fa的输入和输出是以行分割的JSON对象。

创建同步进程

以下这些方法是同步的,意味着他会阻塞事件循环,并暂停执行代码,直到spawned的进程退出。

同步方法简化了任务进程,比如大为简化在应用初始化加载/处理过程。

child_process.spawnSync(command[, args][, options])

  • command {String} 要执行的命令
  • args {Array} 参数列表
  • options {Object}
    • cwd {String} 子进程的当前工作目录
    • input {String|Buffer} 传递给spawned进程的值,这个值将会重写stdio[0]
    • stdio {Array} 子进程的stdio配置。
    • env {Object} 环境变量
    • uid {Number} 设置用户进程的ID。 (参见setuid(2)。)
    • gid {Number} 设置进程组的ID。 (参见setgid(2)。)
    • timeout {Number} 子进程运行最大毫秒数。 (默认: undefined)
    • killSignal {String} 用来终止子进程的信号。 (默认: 'SIGTERM')
    • maxBuffer {Number}
    • encoding {String} stdio输入和输出的编码方式。 (默认: 'buffer')
  • 返回: {Object}
    • pid {Number} 子进程的pid
    • output {Array} stdio输出的结果数组
    • stdout {Buffer|String} output[1]的内容
    • stderr {Buffer|String} output[2]的内容
    • status {Number} 子进程的退出代码
    • signal {String} 用来杀死子进程的信号
    • error {Error} 子进程错误或超时的错误代码

spawnSync直到子进程关闭才会返回。超时或者收到killSignal信号,也不会返回,直到进程完全退出。进程处理完SIGTERM信号后并不会结束,直到子进程完全退出。

child_process.execFileSync(command[, args][, options])

  • command {String} 要执行的命令
  • args {Array} 参数列表
  • options {Object}
    • cwd {String} 子进程的当前工作目录
    • input {String|Buffer}传递给spawned进程的值,这个值将会重写 stdio[0]
    • stdio {Array}子进程的stdio配置。 (默认: 'pipe')
      • stderr 默认情况下会输出给父进程的' stderr除非指定了 stdio
    • env {Object} 环境变量
    • uid {Number} 设置用户进程的ID。 (参见setuid(2)。)
    • gid {Number} 设置进程组的ID。 (参见setgid(2)。)
    • timeout {Number} 进程运行最大毫秒数。 (默认: undefined)
    • killSignal {String} 用来终止子进程的信号。 (默认: 'SIGTERM')
    • maxBuffer {Number}
    • encoding {String} stdio输入和输出的编码方式。 (默认: 'buffer')
  • 返回: {Buffer|String} 来自命令的stdout

直到子进程完全退出,execFileSync才会返回。超时或者收到killSignal信号,也不会返回,直到进程完全退出。进程处理完SIGTERM信号后并不会结束,直到子进程完全退出。

如果进程超时,或者非正常退出,这个方法将会抛出异常。Error会包含整个child_process.spawnSync结果。

child_process.execSync(command[, options])

  • command {String} 要执行的命令
  • options {Object}
    • cwd {String} 子进程的当前工作目录
    • input {String|Buffer} 传递给spawned进程的值,这个值将会重写stdio[0]
    • stdio {Array} 子进程的stdio配置。 (默认: 'pipe')
      • stderr 默认情况下会输出给父进程的' stderr 除非指定了stdio
    • env {Object} 环境变量
    • uid {Number} 设置用户进程的ID。 (参见setuid(2)。)
    • gid {Number} 设置进程组的ID。 (参见setgid(2)。)
    • timeout {Number} 进程运行最大毫秒数。 (默认: undefined)
    • killSignal {String} 用来终止子进程的信号。 (默认: 'SIGTERM')
    • maxBuffer {Number}
    • encoding {String} stdio输入和输出的编码方式。 (默认: 'buffer')
  • 返回: {Buffer|String}来自命令的stdout

直到子进程完全退出,execSync才会返回。超时或者收到killSignal信号,也不会返回,直到进程完全退出。进程处理完SIGTERM信号后并不会结束,直到子进程完全退出。

如果进程超时,或者非正常退出,这个方法将会抛出异常。Error会包含整个child_process.spawnSync结果。

以上就是Node.js官方文档中有关子进程的介绍。


REPL即Node自带的交互式解释器,它可以实现如下的任务:

  • 读取(Read)- 可以读取用户的输入,解析输入的Javascript数据结构并存储在内存中。
  • 执行(Eval)- 可以执行输入的Javascript数据结构。
  • 打印(Print)- 打印输出结果。
  • 循环(Loop)- 对上述的步骤进行循环,如果需要退出,则用户需要两次按下ctrl-c按钮。
稳定性: 3 - 稳定

Read-Eval-Print-Loop (REPL 读取-执行-输出循环)即可作为独立程序,也可以集成到其他程序中。

REPL提供了一种交互的执行JavaScript并查看输出结果的方法。可以用来调试,测试,或仅是用来试试。

在命令行中不带任何参数的执行node,就是REPL模式。它提供了简单的emacs行编辑。

mjr:~$ nodeType '.help' for options.> a = [ 1, 2, 3];[ 1, 2, 3 ]> a.forEach(function (v) {...   console.log(v);...   });123

若想使用高级的编辑模式,使用环境变量NODE_NO_READLINE=1打开node。这样会开启REPL模式,允许你使用rlwrap

例如,你可以添加以下代码到你的bashrc文件里。

alias node="env NODE_NO_READLINE=1 rlwrap node"

repl.start(options)

启动并返回一个REPLServer实例。它继承自[Readline Interface][]。接收的参数"options"有以下值:

  • prompt- 所有输入输出的提示符和流,默认是>.

  • input- 需要监听的可读流,默认为process.stdin.

  • output- 用来输出数据的可写流,默认为process.stdout.

  • terminal- 如果stream被当成TTY,并且有ANSI/VT100转义,传输true。默认在实例的输出流上检查isTTY

  • eval- 用来对每一行进行求值的函数。默认为eval()的异步封装。参见后面的自定义eval例子。

  • useColors- 写函数输出是否有颜色。如果设定了不同的writer函数则无效。默认为 repl 的terminal值。

  • useGlobal- 如果为true,则repl将会使用全局对象,而不是在独立的上下文中运行scripts。默认为false

  • ignoreUndefined- 如果为true,repl不会输出未定义命令的返回值。默认为false

  • writer- 每个命令行被求值时都会调用这个函数,它会返回格式化显示内容(包括颜色)。默认是util.inspect

如果有以下特性,可以使用自己的eval函数:

function eval(cmd, context, filename, callback) {  callback(null, result);}

在同一个node的运行实例上,可以打开多个REPLs。每个都会共享一个全局对象,但会有独立的I/O。

以下的例子,在stdin、 Unix socket和 TCP socket上开启REPL :

var net = require("net"),    repl = require("repl");connections = 0;repl.start({  prompt: "node via stdin> ",  input: process.stdin,  output: process.stdout});net.createServer(function (socket) {  connections += 1;  repl.start({    prompt: "node via Unix socket> ",    input: socket,    output: socket  }).on('exit', function() {    socket.end();  })}).listen("/tmp/node-repl-sock");net.createServer(function (socket) {  connections += 1;  repl.start({    prompt: "node via TCP socket> ",    input: socket,    output: socket  }).on('exit', function() {    socket.end();  });}).listen(5001);

从命令行运行这个程序,将会在stdin上启动REPL。其他的REPL客户端可能通过Unix socket或TCP socket连接。telnet常用于连接TCP socket,socat用于连接Unix和TCP sockets

从Unix socket-based服务器启动REPL(而非stdin),你可以建立长连接,不用重启它们。

通过net.Servernet.Socket实例运行"full-featured" (terminal) REPL的例子,参见: https://gist.github.com/2209310

通过curl(1)实例运行REPL的例子,参见: https://gist.github.com/2053342

Event: 'exit'

function () {}

当用户通过预定义的方式退出REPL将会触发这个事件。预定义的方式包括,在repl里输入.exit,按Ctrl+C两次来发送SIGINT信号,或者在input流上按Ctrl+D 来发送"end"。

监听exit的例子:

r.on('exit', function () {  console.log('Got "exit" event from repl!');  process.exit();});

Event: 'reset'

function (context) {}

重置REPL的上下文的时候触发。当你输入.clear会重置。如果你用{ useGlobal: true }启动repl,那这个事件永远不会被触发。

监听reset的例子:

// Extend the initial repl context.r = repl.start({ options ... });someExtension.extend(r.context);// When a new context is created extend it as well.r.on('reset', function (context) {  console.log('repl has a new context');  someExtension.extend(context);});

REPL 特性

在REPL里, Control+D会退出。可以输入多行表达式。支持全局变量和局部变量的TAB自动补全。

特殊变量_(下划线)包含上一个表达式的结果。

> [ "a", "b", "c" ][ 'a', 'b', 'c' ]> _.length3> _ += 14

REPL支持在全局域里访问任何变量。将变量赋值个和REPLServer关联的上下文对象,你可以显示的讲变量暴露给REPL,例如:

// repl_test.jsvar repl = require("repl"),    msg = "message";repl.start("> ").context.m = msg;

context对象里的东西,会以局部变量的形式出现:

mjr:~$ node repl_test.js> m'message'

有一些特殊的REPL命令:

  • .break - 当你输入多行表达式时,也许你走神了或者不想完成了,.break可以重新开始。
  • .clear - 重置context对象为空对象,并且清空多行表达式。
  • .exit - 关闭输入/输出流,会让REPL退出。
  • .help - 打印这些特殊命令。
  • .save - 保存当前REPL会话到文件。

    .save ./file/to/save.js

  • .load- 加载一个文件到当前REPL会话

    .load ./file/to/load.js

下面的组合键在REPL中有以下效果:

  • <ctrl>C- 和.break键类似,在一个空行连按两次会强制退出。
  • <ctrl>D- 和.exit键类似。


稳定性: 3 - 稳定

如果要在Node.js中使用HTTP服务器或客户端功能,则必须调用require('http')

Node里的HTTP接口支持协议里原本比较难用的特性。特别是很大的或块编码的消息。这些接口不会完全缓存整个请求或响应,这样用户可以在请求或响应中使用数据流。

HTTP消息头对象和下面的例子类似:

{ 'content-length': '123',  'content-type': 'text/plain',  'connection': 'keep-alive',  'host': 'mysite.com',  'accept': '*/*' }

Keys都是小写,值不能修改。

为了能支持尽可能多的HTTP应用程序,Node提供的HTTP API接口都是底层的。仅能处理流和消息。它把消息解析成报文头和报文体,但是不解析实际的报文头和报文体内容。

定义报文头的时候,多个值间可用,分隔。除了set-cookiecookie头,因为它们表示值的数组。诸如content-length的只有一个值的报文头,直接解析,并且只有单值可以表示成已经解析好的对象。

接收到的原始头信息以数组([key, value, key2, value2, ...])的形式保存在rawHeaders里。例如,之前提到的消息对象会有如下的rawHeaders

[ 'ConTent-Length', '123456',  'content-LENGTH', '123',  'content-type', 'text/plain',  'CONNECTION', 'keep-alive',  'Host', 'mysite.com',  'accepT', '*/*' ]

http.METHODS

  • {Array}

解析器支持的HTTP方法列表。

http.STATUS_CODES

  • {Object}

全部标准HTTP响应状态码的和描述的集合。例如,http.STATUS_CODES[404] === 'Not Found'

http.createServer([requestListener])

返回http.Server的新实例。

requestListener函数自动加到'request'事件里。

http.createClient([port][, host])

这个函数已经被抛弃,用http.request()替换。创建一个新的HTTP客户端,连接到服务器的porthost

类: http.Server

这是事件分发器EventEmitter,有以下事件:

事件: 'request'

function (request, response) { }

每当有请求的时候触发。注意:每个连接可以有多个请求(在keep-alive连接中)。requesthttp.IncomingMessage实例,responsehttp.ServerResponse 的实例。

事件: 'connection'

function (socket) { }

当建立新的TCP流的时候。socket是一个net.Socket对象。通常用户不会访问这个事件。协议解析器绑定套接字时采用的方式使套接字不会出发readable事件。也能通过request.connection访问socket

事件: 'close'

function () { }

服务器关闭的时候触发。

事件: 'checkContinue'

function (request, response) { }

当http收到100-continue的http请求时会触发。如果没有监听这个事件,服务器将会自动发送100 Continue的响应。

如果客户端需要继续发送请求主题,或者生成合适的HTTP响应(如,400请求无效),可以通过调用response.writeContinue()来处理。

注意:触发并处理这个事件的时候,不会再触发request事件。

事件: 'connect'

function (request, socket, head) { }

当客户端请求http连接时触发。如果没有监听这个事件,客户端请求连接的时候会被关闭。

  • request是http请求的参数,与request事件参数相同。
  • socket是服务器和客户端间的socket。
  • head是buffer的实例。网络隧道的第一个包,可能为空。

这个事件触发后,请求的socket不会有data事件监听器,也就是说你需要绑定一个监听器到data上,来处理在发送到服务器上的socket数据。

事件: 'upgrade'

function (request, socket, head) { }

当客户端请求http upgrage时候会触发。如果没有监听这个事件,客户端请求一个连接的时候会被关闭。

  • request是http请求的参数,与request事件参数相同。
  • socket是服务器和客户端间的socket。
  • head是buffer的实例。网络隧道的第一个包,可能为空。

这个事件触发后,请求的socket不会有data事件监听器,也就是说你需要绑定一个监听器到data上,来处理在发送到服务器上的socket数据。

事件: 'clientError'

function (exception, socket) { }

如果一个客户端连接触发了一个'error'事件,它就会转发到这里.

socket是导致错误的net.Socket对象。

server.listen(port[, hostname][, backlog][, callback])

在指定的的端口和主机名上开始接收连接。如果忽略主机名,服务器将会接收指向任意的IPv4地址(INADDR_ANY)。

监听一个unix socket,需要提供一个文件名而不是主机名和端口。

积压量backlog为等待连接队列的最大长度。实际的长度由你的操作系统的sysctl设置决定(比如linux上的tcp_max_syn_backlogsomaxconn)。默认参数值为 511 (不是512)

这是异步函数。最后一个参数callback会作为事件监听器添加到listening事件。参见net.Server.listen(port)

server.listen(path[, callback])

启动一个UNIX socket服务器所给路径path

这是异步函数。最后一个参数callback会作为事件监听器添加到listening事件。参见net.Server.listen(port)

server.listen(handle[, callback])

  • handle {Object}
  • callback {Function}

    handle 对象可以是server或socket(任意以下划线_handle开头的成员),或者{fd: <n>}对象。

这会导致server用参数handle接收连接,前提条件是文件描述符或句柄已经连接到端口或域socket。

Windows不能监听文件句柄。

这是异步函数。最后一个参数callback会作为事件监听器添加到listening事件。参见net.Server.listen(port)

server.close([callback])

用于禁止server接收连接。参见net.Server.close().

server.maxHeadersCount

最大请求头的数量限制,默认为1000。如果设置为0,则不做任何限制。

server.setTimeout(msecs, callback)

  • msecs{Number}
  • callback{Function}

为socket设置超时时间,单位为毫秒,如果发生超时,在server对象上触发'timeout'事件,参数为socket。

如果在Server对象上有一个'timeout'事件监听器,超时的时候,将会调用它,参数为socket。

默认情况下,Server的超时为2分钟,如果超时将会销毁socket。如果你给Server的超时事件设置了回调函数,那你就得负责处理socket超时。

server.timeout

  • {Number} Default = 120000 (2 minutes)

超时的时长,单位为毫秒。

注意,socket的超时逻辑在连接时设定,所以有新的连接时才能改变这个值。

设为0时,建立连接的自动超时将失效。

类: http.ServerResponse

这是一个由HTTP服务器(而不是用户)内部创建的对象。作为第二个参数传递给'request'事件。

该响应实现了Writable Stream接口。这是一个包含下列事件的EventEmitter

事件: 'close'

function () { }

在调用response.end(),或准备flush前,底层连接结束。

事件: 'finish'

function () { }

发送完响应触发。响应头和响应体最后一段数据被剥离给操作系统后,通过网络来传输时被触发。这并不代表客户端已经收到数据。

这个事件之后,响应对象不会再触发任何事件。

response.writeContinue()

发送HTTP/1.1 100 Continue消息给客户端,表示请求体可以发送。可以在服务器上查看'checkContinue'事件。

response.writeHead(statusCode[, statusMessage][, headers])

发送一个响应头给请求。状态码是3位数字,如404。最后一个参数headers是响应头。建议第二个参数设置为可以看的懂的消息。

例如:

var body = 'hello world';response.writeHead(200, {  'Content-Length': body.length,  'Content-Type': 'text/plain' });

这个方法仅能在消息中调用一次,而且必须在response.end()前调用。

如果你在这之前调用response.write()response.end(),将会计算出不稳定的头。

Content-Length是字节数,而不是字符数。上面的例子'hello world'仅包含一个字节字符。如果body包含高级编码的字符,则Buffer.byteLength()就必须确定指定编码的字符数。Node不会检查Content-Length和body的长度是否相同。

response.setTimeout(msecs, callback)

  • msecs {Number}
  • callback {Function}

设置socket超时时间,单位为毫秒。如果提供了回调函数,将会在response对象的'timeout'事件上添加监听器。

如果没有给请求、响应、服务器添加'timeout'监视器,超时的时候将会销毁socket。如果你给请求、响应、服务器加了处理函数,就需要负责处理socket超时。

response.statusCode

使用默认的headers 时(没有显式的调用response.writeHead()),这个属性表示将要发送给客户端状态码。

例如:

response.statusCode = 404;

响应头发送给客户端的后,这个属性表示状态码已经发送。

response.statusMessage

使用默认headers时(没有显式的调用response.writeHead()),这个属性表示将要发送给客户端状态信息。如果这个没有定义,将会使用状态码的标准消息。

例如:

response.statusMessage = 'Not found';

当响应头发送给客户端的时候,这个属性表示状态消息已经发送。

response.setHeader(name, value)

设置默认头某个字段内容。如果这个头即将被发送,内容会被替换。如果你想设置更多的头, 就使用一个相同名字的字符串数组。

例如:

response.setHeader("Content-Type", "text/html");

response.setHeader("Set-Cookie", ["type=ninja", "language=javascript"]);

response.headersSent

Boolean(只读)。如果headers发送完毕,则为true,反之为false。

response.sendDate

默认值为true。若为true,当headers里没有Date值时,自动生成Date并发送。

只有在测试环境才能禁用,因为HTTP要求响应包含Date头.

response.getHeader(name)

读取一个在队列中但是还没有被发送至客户端的header。名字是大小写敏感的。仅能再头被flushed前调用。

例如:

var contentType = response.getHeader('content-type');

response.removeHeader(name)

从即将发送的队列里移除头。

例如:

response.removeHeader("Content-Encoding");

response.write(chunk[, encoding][, callback])

如果调用了这个方法,且还没有调用response.writeHead(),将会切换到默认的header,并更新这个header。

这个方法将发送响应体数据块。可能会多次调用这个方法,以提供body成功的部分内容。

chunk可以是字符串或 buffer。如果chunk 是字符串,第二个参数表明如何将它编码成字节流。encoding的默认值是'utf8'。最后一个参数在刷新这个数据块时调用。

注意:这个是原始的HTTP body,和高级的multi-part body编码无关。

第一次调用response.write()的时候,将会发送缓存的头信息和第一个body给客户端。第二次,将会调用response.write()。Node认为你将会独立发送流数据。这意味着,响应缓存在第一个数据块中。

如果成功的刷新全部数据到内核缓冲区,返回true 。如果部分或全部数据在用户内存中还处于排队状况,返回false。当缓存再次释放的时候,将会触发 'drain'

response.addTrailers(headers)

这个方法给响应添加HTTP的尾部header(消息末尾的header)。

只有数据块编码用于响应体时,才会触发Trailers;如果不是(例如,请求是HTTP/1.0),它们将会被自动丢弃。

如果你想触发trailers, HTTP会要求发送Trailer头,它包含一些信息,比如:

response.writeHead(200, { 'Content-Type': 'text/plain',                          'Trailer': 'Content-MD5' });response.write(fileData);response.addTrailers({'Content-MD5': "7895bf4b8828b55ceaf47747b4bca667"});response.end();

response.end([data][, encoding][, callback])

这个方法告诉服务器,所有的响应头和响应体已经发送;服务器可以认为消息结束。response.end()方法必须在每个响应中调用。

如果指定了参数data,将会在响应流结束的时候调用。

http.request(options[, callback])

Node维护每个服务器的连接来生成HTTP请求。这个函数让你可以发布请求。

参数options是对象或字符串。如果options是字符串,会通过url.parse()自动解析。

options值:

  • host: 请求的服务器域名或 IP 地址,默认:'localhost'
  • hostname: 用于支持url.parse()hostname优于host
  • port: 远程服务器端口。 默认为:80.
  • localAddress: 用于绑定网络连接的本地接口
  • socketPath: Unix域socket(使用host:port或socketPath)
  • method: 指定 HTTP 请求方法。 默认: 'GET'.
  • path: 请求路径。 默认为:'/'。如果有查询字符串,则需要包含。例如,'/index.html?page=12'。请求路径包含非法字符时抛出异常。目前,只有空格不行,不过在未来可能改变。
  • headers: 包含请求头的对象
  • auth: 用于计算认证头的基本认证,即user:password
  • agent: 控制Agent的行为。当使用了一个Agent的时候,请求将默认为Connection: keep-alive。可能的值为:
    • undefined (default): 在这个主机和端口上使用global Agent
    • Agent object: 在Agent中显式使用passed .
    • false: 选择性停用连接池,默认请求为:Connection: close.
    • keepAlive: {Boolean} 持资源池周围的socket,用于未来其它请求。默认值为false
    • keepAliveMsecs: {Integer} 使用HTTP KeepAlive的时候,通过正在保持活动的sockets发送TCP KeepAlive包的频繁程度。默认值为1000。仅当keepAlive为true时才相关。

可选参数callback将会作为一次性的监视器,添加给 'response' 事件。

http.request()返回一个http.ClientRequest类的实例。ClientRequest实例是一个可写流对象。如果需要用POST请求上传一个文件的话,就将其写入到ClientRequest对象。

例如:

var postData = querystring.stringify({  'msg' : 'Hello World!'});var options = {  hostname: 'www.google.com',  port: 80,  path: '/upload',  method: 'POST',  headers: {    'Content-Type': 'application/x-www-form-urlencoded',    'Content-Length': postData.length  }};var req = http.request(options, function(res) {  console.log('STATUS: ' + res.statusCode);  console.log('HEADERS: ' + JSON.stringify(res.headers));  res.setEncoding('utf8');  res.on('data', function (chunk) {    console.log('BODY: ' + chunk);  });});req.on('error', function(e) {  console.log('problem with request: ' + e.message);});// write data to request bodyreq.write(postData);req.end();

注意,例子里调用了req.end()http.request()必须调用req.end()来表明请求已经完成,即使没有数据写入到请求body里。

如果在请求的时候遇到错误(DNS解析、TCP级别的错误或实际HTTP解析错误),在返回的请求对象时会触发一个'error'事件。

有一些特殊的头需要注意:

  • 发送Connection: keep-alive告诉服务器保持连接,直到下一个请求到来。

  • 发送Content-length头将会禁用chunked编码。

  • 发送一个Expect头,会立即发送请求头,一般来说,发送Expect: 100-continue,你必须设置超时,并监听continue事件。更多细节参见RFC2616 Section 8.2.3。

  • 发送一个授权头,将会使用auth参数重写,来计算基本的授权。

http.get(options[, callback])

因为多数请求是没有报文体的GET请求,Node提供了这个简便的方法。和http.request()唯一不同点在于,这个方法自动设置GET,并自动调用req.end()

例如:

http.get("http://www.google.com/index.html", function(res) {  console.log("Got response: " + res.statusCode);}).on('error', function(e) {  console.log("Got error: " + e.message);});

类: http.Agent

HTTP Agent用于socket池,用于HTTP客户端请求。

HTTP Agent也把客户端请求默认为使用Connection:keep-alive 。如果没有HTTP请求正在等着成为空闲socket的话,那么socket将关闭。这意味着,Node的资源池在负载的情况下对keep-alive有利,但是仍然不需要开发人员使用KeepAlive来手动关闭HTTP客户端。

如果你选择使用HTTP KeepAlive, 可以创建一个Agent对象,将 flag 设置为true. (参见下面的constructor options ) ,这样Agent会把没用到的socket放到池里,以便将来使用。他们会被显式的标志,让Node不运行。但是,当不再使用它的时候,需要显式的调用destroy(),这样socket将会被关闭。

当socket事件触发close事件或特殊的agentRemove事件时,socket将会从agent池里移除。如果你要保持HTTP请求保持长时间打开,并且不希望他们在池里,可以参考以下代码:

http.get(options, function(res) {  // Do stuff}).on("socket", function (socket) {  socket.emit("agentRemove");});

另外,你可以使用agent:false让资源池停用:

http.get({  hostname: 'localhost',  port: 80,  path: '/',  agent: false  // create a new agent just for this one request}, function (res) {  // Do stuff with response})

new Agent([options])

  • options {Object} agent上的设置选项集合,有以下字段内容:
    • keepAlive {Boolean} 持资源池周围的socket,用于未来其它请求。默认值为false
    • keepAliveMsecs {Integer} 使用HTTP KeepAlive的时候,通过正在保持活动的sockets发送TCP KeepAlive包的频繁程度。默认值为1000。仅当 keepAlive为true时才相关。
    • maxSockets {Number}在空闲状态下,还依然开启的socket的最大值。仅当keepAlive设置为true的时候有效。默认值为256。

http.request使用的默认的http.globalAgent,会设置全部的值为默认。

必须在创建你自己的Agent对象后,才能配置这些值。

var http = require('http');var keepAliveAgent = new http.Agent({ keepAlive: true });options.agent = keepAliveAgent;http.request(options, onResponseCallback);

agent.maxSockets

默认值为Infinity。决定了每台主机上的agent可以拥有的并发socket的打开数量,主机可以是host:porthost:port:localAddress

agent.maxFreeSockets

默认值256.对于支持HTTP KeepAlive的Agent而言,这个方法设置了空闲状态下仍然打开的套接字数的最大值。

agent.sockets

这个对象包含了当前Agent使用中的socket数组。不要修改它。

agent.freeSockets

使用HTTP KeepAlive的时候,这个对象包含等待当前Agent使用的socket数组。不要修改它。

agent.requests

这个对象包含了还没分配给socket的请求数组。不要修改它。

agent.destroy()

销毁任意一个被agent使用的socket。

通常情况下不要这么做。如果你正在使用一个允许KeepAlive的agent,当你知道不在使用它的时候,最好关闭agent。否则,socket会一直保存打开状态,直到服务器关闭。

agent.getName(options)

获取一组请求选项的唯一名,来确定某个连接是否可重用。在http agent里,它会返回host:port:localAddress。在http agent里, name包括CA,cert, ciphers, 和其他HTTPS/TLS特殊选项来决定socket是否可以重用。

http.globalAgent

Agent的全局实例,是http客户端的默认请求。

类:http.ClientRequest

该对象在内部创建并从http.request()返回。他是正在处理的请求,其头部已经在队列中。使用setHeader(name, value),getHeader(name), removeHeader(name)API可以改变header。当关闭连接的时候,header将会和第一个数据块一起发送。

为了获取响应,可以给请求对象的'response'添加监听器。当接收到响应头的时候将会从请求对象里触发'response''response'事件执行时有一个参数,该参数为http.IncomingMessage的实例。

'response'事件期间,可以给响应对象添加监视器,监听'data'事件。

如果没有添加'response'处理函数,响应将被完全忽略。如果你添加了'response'事件处理函数,那你必须消费掉从响应对象获取的数据,可以在 'readable'事件里调用response.read(),或者添加一个'data'处理函数,或者调用.resume()方法。如果未读取数据,它将会消耗内存,最终产生 process out of memory错误。

Node不会检查Content-Length和body的长度是否相同。

该请求实现了Writable Stream接口。这是一个包含下列事件的EventEmitter

事件: 'response'

function (response) { }

当接收到请求的时候会触发,仅会触发一次。response的参数是http.IncomingMessage的实例。

Options:

  • host: 要请求的服务器域名或IP地址
  • port: 远程服务器的端口
  • socketPath: Unix域Socket (使用host:port或socketPath之一)

事件: 'socket'

function (socket) { }

Socket附加到这个请求的时候触发。

事件: 'connect'

function (response, socket, head) { }

每次服务器使用CONNECT方法响应一个请求时触发。如果这个这个事件未被监听,接收CONNECT方法的客户端将关闭他们的连接。

下面的例子展示了一对匹配的客户端/服务器如何监听connect事件。var http = require('http');var net = require('net');var url = require('url');

// Create an HTTP tunneling proxyvar proxy = http.createServer(function (req, res) {  res.writeHead(200, {'Content-Type': 'text/plain'});  res.end('okay');});proxy.on('connect', function(req, cltSocket, head) {  // connect to an origin server  var srvUrl = url.parse('http://' + req.url);  var srvSocket = net.connect(srvUrl.port, srvUrl.hostname, function() {    cltSocket.write('HTTP/1.1 200 Connection Established
' +                    'Proxy-agent: Node-Proxy
' +                    '
');    srvSocket.write(head);    srvSocket.pipe(cltSocket);    cltSocket.pipe(srvSocket);  });});// now that proxy is runningproxy.listen(1337, '127.0.0.1', function() {  // make a request to a tunneling proxy  var options = {    port: 1337,    hostname: '127.0.0.1',    method: 'CONNECT',    path: 'www.google.com:80'  };  var req = http.request(options);  req.end();  req.on('connect', function(res, socket, head) {    console.log('got connected!');    // make a request over an HTTP tunnel    socket.write('GET / HTTP/1.1
' +                 'Host: www.google.com:80
' +                 'Connection: close
' +                 '
');    socket.on('data', function(chunk) {      console.log(chunk.toString());    });    socket.on('end', function() {      proxy.close();    });  });});

事件: 'upgrade'

function (response, socket, head) { }

每当服务器响应upgrade请求时触发。如果没有监听这个事件,客户端会收到upgrade头后关闭连接。

下面的例子展示了一对匹配的客户端/服务器如何监听upgrade事件。

var http = require('http');// Create an HTTP servervar srv = http.createServer(function (req, res) {  res.writeHead(200, {'Content-Type': 'text/plain'});  res.end('okay');});srv.on('upgrade', function(req, socket, head) {  socket.write('HTTP/1.1 101 Web Socket Protocol Handshake
' +               'Upgrade: WebSocket
' +               'Connection: Upgrade
' +               '
');  socket.pipe(socket); // echo back});// now that server is runningsrv.listen(1337, '127.0.0.1', function() {  // make a request  var options = {    port: 1337,    hostname: '127.0.0.1',    headers: {      'Connection': 'Upgrade',      'Upgrade': 'websocket'    }  };  var req = http.request(options);  req.end();  req.on('upgrade', function(res, socket, upgradeHead) {    console.log('got upgraded!');    socket.end();    process.exit(0);  });});

事件: 'continue'

function () { }

当服务器发送'100 Continue' HTTP响应的时候触发,通常因为请求包含'Expect: 100-continue'。该指令表示客户端应发送请求体。

request.flushHeaders()

刷新请求的头。

考虑效率因素,Node.js通常会缓存请求的头直到你调用request.end(),或写入请求的第一个数据块。然后,包装请求的头和数据到一个独立的TCP包里。

request.write(chunk[, encoding][, callback])

发送一个请求体的数据块。通过多次调用这个函数,用户能流式的发送请求给服务器,这种情况下,建议使用['Transfer-Encoding', 'chunked']头。

chunk参数必须是Buffer或字符串。

回调参数可选,当这个数据块被刷新的时候会被调用。

request.end([data][, encoding][, callback])

发送请求完毕。如果body的数据没被发送,将会将他们刷新到流里。如果请求是分块的,该方法会发送终结符0 。

如果指定了data,等同于先调用request.write(data, encoding),再调用request.end(callback)

如果有callback,将会在请求流结束的时候调用。

request.abort()

终止一个请求. (v0.3.8开始新加入)。

request.setTimeout(timeout[, callback])

如果socket被分配给这个请求,并完成连接,将会调用socket.setTimeout()

request.setNoDelay([noDelay])

如果socket被分配给这个请求,并完成连接,将会调用socket.setNoDelay()

request.setSocketKeepAlive([enable][, initialDelay])

如果socket被分配给这个请求,并完成连接,将会调用socket.setKeepAlive()

http.IncomingMessage

http.Serverhttp.ClientRequest创建了IncomingMessage对象,作为第一个参数传递给'response'。它可以用来访问应答的状态,头文件和数据。

它实现了Readable Stream接口,以及以下额外的事件,方法和属性。

事件: 'close'

function () { }

表示底层连接已经关闭。和'end'类似,这个事件每个应答只会发送一次。

message.httpVersion

客户端向服务器发送请求时,客户端发送的 HTTP 版本;或者服务器想客户端返回应答时,服务器的HTTP版本。通常是'1.1''1.0'

另外,response.httpVersionMajor是第一个整数,response.httpVersionMinor是第二个整数。

message.headers

请求/响应头对象。

只读的头名称和值的映射。头的名字是小写,比如:

// Prints something like://// { 'user-agent': 'curl/7.22.0',//   host: '127.0.0.1:8000',//   accept: '*/*' }console.log(request.headers);

message.rawHeaders

接收到的请求/响应头字段列表。

注意,键和值在同一个列表中。它并非一个元组列表。所以,偶数偏移量为键,奇数偏移量为对应的值。

头名字不是小写敏感,也没用合并重复的头。

// Prints something like://// [ 'user-agent',//   'this is invalid because there can be only one',//   'User-Agent',//   'curl/7.22.0',//   'Host',//   '127.0.0.1:8000',//   'ACCEPT',//   '*/*' ]console.log(request.rawHeaders);

message.trailers

请求/响应的尾部对象。只在'end'事件中存在。

message.rawTrailers

接收到的原始的请求/响应尾部键和值。仅在'end'事件中存在。

message.setTimeout(msecs, callback)

  • msecs {Number}
  • callback {Function}

调用message.connection.setTimeout(msecs, callback).

message.method

仅对从http.Server获得的请求有效。

请求方法如果一个只读的字符串。例如:'GET','DELETE'.

message.url

仅对从http.Server获得的请求有效。

请求的URL字符串。它仅包含实际的HTTP请求中所提供的URL,比如请求如下:

GET /status?name=ryan HTTP/1.1
Accept: text/plain

request.url 就是:

'/status?name=ryan'

如果你想将URL分解,可以用require('url').parse(request.url),例如:

node> require('url').parse('/status?name=ryan'){ href: '/status?name=ryan',  search: '?name=ryan',  query: 'name=ryan',  pathname: '/status' }

如果想从查询字符串中解析出参数,可以用require('querystring').parse函数,或者将true作为第二个参数传递给require('url').parse。 例如:

node> require('url').parse('/status?name=ryan', true){ href: '/status?name=ryan',  search: '?name=ryan',  query: { name: 'ryan' },  pathname: '/status' }

message.statusCode

仅对从http.ClientRequest获取的响应有效。

3位数的HTTP响应状态码404

message.statusMessage

仅对从http.ClientRequest获取的响应有效。

HTTP的响应消息。比如,OKInternal Server Error.

message.socket

和连接相关联的net.Socket对象。

通过HTTPS支持,使用request.connection.verifyPeer()request.connection.getPeerCertificate()获取客户端的身份信息。


稳定性: 2 - 不稳定

单个Node.js实例在单线程中运行,在某些情况下,它可能出现负载,因此为了能够更好的利用多核系统的能力,你可以使用Node.js内置的集群(cluster)功能来处理负载。

在集群模块里很容易就能创建一个共享所有服务器接口的进程。

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  // In this case its a HTTP server  http.createServer(function(req, res) {    res.writeHead(200);    res.end("hello world
");  }).listen(8000);}

运行Node后,将会在所有工作进程里共享8000端口。

% NODE_DEBUG=cluster node server.js23521,Master Worker 23524 online23521,Master Worker 23526 online23521,Master Worker 23523 online23521,Master Worker 23528 online

这个特性是最近才引入的,大家可以试试并提供反馈。

还要注意,在Windows系统里还不能在工作进程中创建一个被命名的管道服务器。

如何工作

child_process.fork方法派生工作进程,所以它能通过IPC和父进程通讯,并相互传递句柄。

集群模块通过以下的两种分发模式来处理连接:

第一种(默认方法,除了Windows平台)为循环式。主进程监听一个端口,接收新的连接,再轮流的分发给工作进程。

第二种,主进程监听socket,并发送给感兴趣的工作进程,工作进程直接接收连接。

比较上述两种方法,第二种方法理论上性能最高。实际上,由于操作系统各式各样,分配往往分配不均。比如,70%的连接终止于2个进程,实际上共有8个进程。

因为server.listen()将大部分工作交给了主进程,所以一个普通的Node.js进程和一个集群工作进程会在三种情况下有所区别:

  1. server.listen({fd: 7})由于消息被传回主进程,所以将会监听主进程里的文件描述符,而不是其他工作进程里的文件描述符 7。
  2. server.listen(handle)监听一个明确地句柄,会使得工作进程使用指定句柄,而不是与主进程通讯。如果工作进程已经拥有了该句柄,前提是您知道在做什么。
  3. server.listen(0)通常它会让服务器随机监听端口。然而在集群里每个工作进程listen(0)时会收到相同的端口。实际上仅第一次是随机的,之后是可预测的。如果你想监听一个特定的端口,可以根据集群的工作进程的ID生产一个端口ID 。

在Node.js或你的程序里没有路由逻辑,工作进程见也没有共享状态。因此,像登录和会话这样的工作,不要设计成过度依赖内存里的对象。

因为工作线程都是独立的,你可以根据需求来杀死或者派生而不会影响其他进程。只要仍然有工作进程,服务器还会接收连接。Node不会自动管理工作进程的数量,这是你的责任,你可以根据自己需求来管理。

cluster.schedulingPolicy

调度策略cluster.SCHED_RR表示轮流制,cluster.SCHED_NONE表示操作系统处理。这是全局性的设定,一旦你通过cluster.setupMaster()派生了第一个工作进程,它就不可更改了。

SCHED_RR是除Windows外所有系统的默认设置。只要libuv能够有效地分配IOCP句柄并且不产生巨大的性能损失,Windows也会改为SCHED_RR方式。

cluster.schedulingPolicy也可通过环境变量NODE_CLUSTER_SCHED_POLICY来更改。有效值为"rr""none"

cluster.settings

  • {Object}
    • execArgv {Array} 传给可执行的Node的参数列表(默认=process.execArgv)
    • exec {String} 执行文件的路径。 (默认=process.argv[1])
    • args {Array} 传给工作进程的参数列表(默认=process.argv.slice(2))
    • silent {Boolean}是否将输出发送给父进程的stdio。(默认=false)
    • uid {Number} 设置用户进程的ID。 (参考setuid(2)。)
    • gid {Number} 设置进程组的ID。 (参考setgid(2)。)

调用.setupMaster()(或.fork())方法后,这个settings对象会包含设置内容,包括默认值。

设置后会立即冻结,因为.setupMaster()只能调用一次。

这个对象不应该被手动改变或设置。

cluster.isMaster

  • {Boolean}

如果是主进程,返回true。如果process.env.NODE_UNIQUE_ID未定义,则isMastertrue

cluster.isWorker

  • {Boolean}

如果不是主进程返回true(和cluster.isMaster相反)。

事件: 'fork'

  • worker {Worker object}

当一个新的工作进程被分支出来,集群模块会产生'fork'事件。它可用于记录工作进程,并创建自己的超时管理。

var timeouts = [];function errorMsg() {  console.error("Something must be wrong with the connection ...");}cluster.on('fork', function(worker) {  timeouts[worker.id] = setTimeout(errorMsg, 2000);});cluster.on('listening', function(worker, address) {  clearTimeout(timeouts[worker.id]);});cluster.on('exit', function(worker, code, signal) {  clearTimeout(timeouts[worker.id]);  errorMsg();});

事件: 'online'

  • worker {Worker object}

分支出一个新的工作进程后,它会响应在线消息。当主线程接收到在线消息后,它会触发这个事件。'fork'和'online'之间的区别在于,主进程分支一个工作进程后会调用 fork,而工作进程运行后会调用emitted。

cluster.on('online', function(worker) {  console.log("Yay, the worker responded after it was forked");});

事件: 'listening'

  • worker {Worker object}
  • address {Object}

工作进程调用listen()时,服务器会触发'listening'事件,同时也会在主进程的集群里触发。

事件处理函数有两个参数,worker包含工作进程对象,address包含以下属性:address, portaddressType。如果工作进程监听多个地址的时候,这些东西非常有用。

cluster.on('listening', function(worker, address) {  console.log("A worker is now connected to " + address.address + ":" + address.port);});

addressType是以下内容:

  • 4 (TCPv4)
  • 6 (TCPv6)
  • -1 (unix domain socket)
  • "udp4" 或者"udp6"(UDP v4或者v6)*

事件: 'disconnect'

  • worker {Worker object}

当一个工作进程的IPC通道关闭时会触发这个事件。当工作进程正常退出,被杀死,或者手工关闭(例如worker.disconnect())时会调用。

disconnectexit事件间可能存在延迟。 这些事件可以用来检测进程是否卡在清理过程中,或者存在长连接。

cluster.on('disconnect', function(worker) {  console.log('The worker #' + worker.id + ' has disconnected');});

事件: 'exit'

  • worker {Worker object}
  • code {Number} 如果正常退出,则为退出代码.
  • signal {String} 使得进程被杀死的信号名 (比如,'SIGHUP')

当任意一个工作进程终止的时候,集群模块会触发'exit'事件。

可以调用.fork()重新启动工作进程。

cluster.on('exit', function(worker, code, signal) {  console.log('worker %d died (%s). restarting...',    worker.process.pid, signal || code);  cluster.fork();});

参见child_process event: 'exit'.

事件: 'setup'

  • settings{Object}

调用.setupMaster()后会被触发。

settings对象就是cluster.settings对象。

详细内容参见cluster.settings

cluster.setupMaster([settings])

  • settings {Object}
    • exec {String} 执行文件的路径。 (默认=process.argv[1])
    • args {Array}传给工作进程的参数列表(默认=process.argv.slice(2))
    • silent {Boolean} 是否将输出发送给父进程的stdio.

setupMaster用来改变默认的'fork' 。 一旦调用,settings值将会出现在cluster.settings里。

你需要注意如下事项:

  • 改变任何设置,仅会对未来的工作进程产生影响,不会影响对目前已经运行的进程
  • 工作进程里,仅能改变传递给.fork()env属性。
  • 以上的默认值,仅在第一次调用的时候有效,之后的默认值是调用cluster.setupMaster()后的值。

例如:

var cluster = require('cluster');cluster.setupMaster({  exec: 'worker.js',  args: ['--use', 'https'],  silent: true});cluster.fork(); // https workercluster.setupMaster({  args: ['--use', 'http']});cluster.fork(); // http worker

仅能在主进程里调用。

cluster.fork([env])

  • env{Object} 添加到子进程环境变量中的键值。
  • return {Worker object}

派生一个新的工作进程。

仅能在主进程里调用。

cluster.disconnect([callback])

  • callback {Function} 当所有工作进程都断开连接,并且关闭句柄后被调用。

cluster.workers里的每个工作进程可调用.disconnect()关闭。

关闭所有的内部句柄连接,并且没有任何等待处理的事件时,允许主进程优雅的退出。

这个方法有一个可选参数,会在完成时被调用。

仅能在主进程里调用。

cluster.worker

  • {Object}

对当前工作进程对象的引用。主进程中不可用。

var cluster = require('cluster');if (cluster.isMaster) {  console.log('I am master');  cluster.fork();  cluster.fork();} else if (cluster.isWorker) {  console.log('I am worker #' + cluster.worker.id);}

cluster.workers

  • {Object}

存储活跃工作对象的哈希表,主键是id,能方便的遍历所有工作进程,仅在主进程可用。

当工作进程关闭连接并退出后,将会从cluster.workers里移除。这两个事件的次序无法确定,仅能保证从cluster.workers移除会发生在'disconnect''exit'之后。

// Go through all workersfunction eachWorker(callback) {  for (var id in cluster.workers) {    callback(cluster.workers[id]);  }}eachWorker(function(worker) {  worker.send('big announcement to all workers');});

如果希望通过通讯通道引用工作进程,那么使用工作进程的 id 来查询最简单。

socket.on('data', function(id) {  var worker = cluster.workers[id];});

Class: Worker

一个Worker对象包含工作进程所有公开的信息和方法。在主进程里可用通过cluster.workers来获取,在工作进程里可以通过cluster.worker来获取。

worker.id

  • {String}

每一个新的工作进程都有独立的唯一标示,它就是id

当工作进程可用时,id就是cluster.workers里的主键。

worker.process

  • {ChildProcess object}

所有工作进程都是通用child_process.fork()创建的,该函数返回的对象被储存在process中。

参见: Child Process module

注意:当process.suicide不是true的时候,会触发'disconnect'事件,并使得工作进程调用process.exit(0)。它会保护意外的连接关闭。

worker.suicide

  • {Boolean}

调用.kill().disconnect()后设置,在这之前是undefined

worker.suicide能让你区分出是自愿的还是意外退出,主进程可以根据这个值,来决定是否是重新派生成工作进程。

cluster.on('exit', function(worker, code, signal) {  if (worker.suicide === true) {    console.log('Oh, it was just suicide' – no need to worry').  }});// kill workerworker.kill();

worker.send(message[, sendHandle])

  • message {Object}
  • sendHandle {Handle object}

这个函数和child_process.fork()提供的send方法相同。主进程里你必须使用这个函数给指定工作进程发消息。

在工作进程里,你也可以用process.send(message)

这个例子会回应所有来自主进程的消息:

if (cluster.isMaster) {  var worker = cluster.fork();  worker.send('hi there');} else if (cluster.isWorker) {  process.on('message', function(msg) {    process.send(msg);  });}

worker.kill([signal='SIGTERM'])

  • signal{String}发送给工作进程的杀死信号的名称

这个函数会杀死工作进程。在主进程里,它会关闭worker.process,一旦关闭会发送杀死信号。在工作进程里,关闭通道,退出,返回代码0

会导致.suicide被设置。

为了保持兼容性,这个方法的别名是worker.destroy()

注意,在工作进程里有process.kill(),于此不同。

worker.disconnect()

在工作进程里,这个函数会关闭所有服务器,等待 'close' 事件,关闭IPC通道。

在主进程里,发给工作进程一个内部消息,用来调用.disconnect()

会导致.suicide被设置。

注意,服务器关闭后,不再接受新的连接,但可以接受新的监听。已经存在的连接允许正常退出。当连接为空得时候,工作进程的IPC通道运行优雅的退出。

以上仅能适用于服务器的连接,客户端的连接由工作进程关闭。

注意,在工作进程里,存在process.disconnect,但并不是这个函数,它是disconnect。

由于长连接可能会阻塞进程关闭连接,有一个较好的办法是发消息给应用,这样应用会想办法关闭它们。超时管理也是不错,如果超过一定时间后还没有触发 disconnect事件,将会杀死进程。

if (cluster.isMaster) {  var worker = cluster.fork();  var timeout;  worker.on('listening', function(address) {    worker.send('shutdown');    worker.disconnect();    timeout = setTimeout(function() {      worker.kill();    }, 2000);  });  worker.on('disconnect', function() {    clearTimeout(timeout);  });} else if (cluster.isWorker) {  var net = require('net');  var server = net.createServer(function(socket) {    // connections never end  });  server.listen(8000);  process.on('message', function(msg) {    if(msg === 'shutdown') {      // initiate graceful close of any connections to server    }  });}

worker.isDead()

工作进程结束,返回true, 否则返回false

worker.isConnected()

当工作进程通过IPC通道连接主进程时,返回true ,否则false。工作进程创建后会连接到主进程。当disconnect事件触发后会关闭连接。

事件: 'message'

  • message{Object}

该事件和child_process.fork()所提供的一样。在主进程中您应当使用该事件,而在工作进程中您也可以使用process.on('message')

例如,有一个集群使用消息系统在主进程中统计请求的数量:

var cluster = require('cluster');var http = require('http');if (cluster.isMaster) {  // Keep track of http requests  var numReqs = 0;  setInterval(function() {    console.log("numReqs =", numReqs);  }, 1000);  // Count requestes  function messageHandler(msg) {    if (msg.cmd && msg.cmd == 'notifyRequest') {      numReqs += 1;    }  }  // Start workers and listen for messages containing notifyRequest  var numCPUs = require('os').cpus().length;  for (var i = 0; i < numCPUs; i++) {    cluster.fork();  }  Object.keys(cluster.workers).forEach(function(id) {    cluster.workers[id].on('message', messageHandler);  });} else {  // Worker processes have a http server.  http.Server(function(req, res) {    res.writeHead(200);    res.end("hello world
");    // notify master about the request    process.send({ cmd: 'notifyRequest' });  }).listen(8000);}

事件: 'online'

cluster.on('online')事件类似, 仅能在特定工作进程里触发。

cluster.fork().on('online', function() {  // Worker is online});

不会在工作进程里触发。

事件: 'listening'

  • address{Object}

cluster.on('listening')事件类似, 仅能在特定工作进程里触发。

cluster.fork().on('listening', function(address) {  // Worker is listening});

不会在工作进程里触发。

事件: 'disconnect'

cluster.on('disconnect')事件类似, 仅能在特定工作进程里触发。

cluster.fork().on('disconnect', function() {  // Worker has disconnected});

事件: 'exit'

  • code {Number} 正常退出时的退出代码.
  • signal {String} 使得进程被终止的信号的名称(比如SIGHUP)。

cluster.on('exit')事件类似, 仅能在特定工作进程里触发。

var worker = cluster.fork();worker.on('exit', function(code, signal) {  if( signal ) {    console.log("worker was killed by signal: "+signal);  } else if( code !== 0 ) {    console.log("worker exited with error code: "+code);  } else {    console.log("worker success!");  }});

事件: 'error'

child_process.fork()事件类似。

工作进程里,你也可以用process.on('error')


稳定性: 1 - 试验

类: smalloc

表示能够通过简单的内存分配器(处理扩展原始内存的分配)支持的缓存,可供Smalloc使用的函数如下所示:

smalloc.alloc(length[, receiver][, type])

  • length {Number} <= smalloc.kMaxLength
  • receiver {Object} 默认: new Object
  • type {Enum} 默认: Uint8

此函数的作用为返回包含分配的外部数组数据的receiver对象。如果没有传入receiver,则将会创建并返回一个新的对象。

这可以用来创建你自己的类似buffer的类。不会设置其他属性,因此使用者需要跟踪其他所需信息(比如分配的长度)。

function SimpleData(n) {  this.length = n;  smalloc.alloc(this.length, this);}SimpleData.prototype = { /* ... */ };

仅检查receiver是否是非数组的对象。因此,可以分配扩展数据数据,不仅是普通对象。

function allocMe() { }smalloc.alloc(3, allocMe);// { [Function allocMe] '0': 0, '1': 0, '2': 0 }

v8不支持给数组分配扩展数组对象,如果这么做,将会抛出。

你可以指定外部数组数据的类型,在smalloc.Types列出了可供使用的外部数组数据的类型,例如:

var doubleArr = smalloc.alloc(3, smalloc.Types.Double);for (var i = 0; i < 3; i++)  doubleArr = i / 10;// { '0': 0, '1': 0.1, '2': 0.2 }

使用Object.freeze,Object.sealObject.preventExtensions不能冻结、封锁、阻止对象的使用扩展数据扩展。

smalloc.copyOnto(source, sourceStart, dest, destStart, copyLength);

  • source {Object} 分配了外部数组的对象
  • sourceStart {Number} 负责的起始位置
  • dest {Object} 分配了外部数组的对象
  • destStart {Number} 拷贝到目标的起始位置
  • copyLength {Number} 需要拷贝的长度

将内存从一个外部数组分配复制到另一个数组中,任何参数都是可选的,否则会抛出异常。

var a = smalloc.alloc(4);var b = smalloc.alloc(4);for (var i = 0; i < 4; i++) {  a[i] = i;  b[i] = i * 2;}// { '0': 0, '1': 1, '2': 2, '3': 3 }// { '0': 0, '1': 2, '2': 4, '3': 6 }smalloc.copyOnto(b, 2, a, 0, 2);// { '0': 4, '1': 6, '2': 2, '3': 3 }

copyOnto将自动检测内部分配的长度,因此不需要设置任何附加参数。

smalloc.dispose(obj)

  • obj Object

释放通过smalloc.alloc给对象分配的内存。

var a = {};smalloc.alloc(3, a);// { '0': 0, '1': 0, '2': 0 }smalloc.dispose(a);// {}

这对于减轻垃圾回收器的负担是有效的,但是在开发的时候还是要小心,程序里可能会出现难以跟踪的错误。

var a = smalloc.alloc(4);var b = smalloc.alloc(4);// perform this somewhere along the linesmalloc.dispose(b);// now trying to copy some data outsmalloc.copyOnto(b, 2, a, 0, 2);// now results in:// RangeError: copy_length > source_length

调用dispose(),对象依旧拥有外部数据,例如smalloc.hasExternalData()会返回truedispose()不支持缓存,如果传入将会抛出。

smalloc.hasExternalData(obj)

  • obj {Object}

如果obj拥有外部分配的内存,返回true

smalloc.kMaxLength

可分配的最大数量。则同样适用于缓冲区的创建。

smalloc.Types

外部数组的类型,包含:

  • Int8
  • Uint8
  • Int16
  • Uint16
  • Int32
  • Uint32
  • Float
  • Double
  • Uint8Clamped


稳定性: 3 - 稳定

HTTPS是什么?HTTPS是基于TLS/SSL的HTTP协议,在Node.js里它可以作为单独的模块来实现。在本文中,你将了解HTTPS的使用方法。

类: https.Server

https.Server是tls.Server的子类,并且和http.Server一样触发事件。更多信息参见http.Server

server.setTimeout(msecs, callback)

详情参见http.Server#setTimeout().

server.timeout

详情参见http.Server#timeout.

https.createServer(options[, requestListener])

返回一个新的HTTPS服务器对象。其中options类似于 [tls.createServer()][tls.md#tls_tls_createserver_options_secureconnectionlistener]。 requestListener函数自动加到'request'事件里。

例如:

// curl -k https://localhost:8000/var https = require('https');var fs = require('fs');var options = {  key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),  cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem')};https.createServer(options, function (req, res) {  res.writeHead(200);  res.end("hello world
");}).listen(8000);

或:

var https = require('https');var fs = require('fs');var options = {  pfx: fs.readFileSync('server.pfx')};https.createServer(options, function (req, res) {  res.writeHead(200);  res.end("hello world
");}).listen(8000);

server.listen(port[, host][, backlog][, callback])

server.listen(path[, callback])

server.listen(handle[, callback])

详情参见http.listen()

server.close([callback])

详情参见http.close()

https.request(options, callback)

可以给安全web服务器发送请求。

options可以是一个对象或字符串。如果options是字符串,则会被url.parse()解析。

所有来自http.request()选项都是经过验证的。

例如:

var https = require('https');var options = {  hostname: 'encrypted.google.com',  port: 443,  path: '/',  method: 'GET'};var req = https.request(options, function(res) {  console.log("statusCode: ", res.statusCode);  console.log("headers: ", res.headers);  res.on('data', function(d) {    process.stdout.write(d);  });});req.end();req.on('error', function(e) {  console.error(e);});

option参数具有以下的值:

  • host: 请求的服务器域名或IP地址,默认:'localhost'
  • hostname: 用于支持url.parse()hostname优于host
  • port: 远程服务器端口。默认: 443。
  • method: 指定HTTP请求方法。默认:'GET'
  • path: 请求路径。默认:'/'。如果有查询字符串,则需要包含。比如'/index.html?page=12'
  • headers: 包含请求头的对象
  • auth: 用于计算认证头的基本认证,即user:password
  • agent: 控制Agent的行为。当使用了一个Agent的时候,请求将默认为Connection: keep-alive。可能的值为:
    • undefined (default): 在这个主机和端口上使用[global Agent][]
    • Agent object: 在Agent中显式使用passed.
    • false: 选择性停用连接池,默认请求为:Connection: close

tls.connect()的参数也能指定。但是,globalAgent会忽略他们。

  • pfx: SSL使用的证书,私钥,和证书Certificate,默认为null.
  • key: SSL使用的私钥. 默认为null.
  • passphrase: 私钥或pfx的口令字符串. 默认为null.
  • cert: 所用公有x509证书. 默认为null.
  • ca: 用于检查远程主机的证书颁发机构或包含一系列证书颁发机构的数组。
  • ciphers: 描述要使用或排除的密码的字符串,格式请参阅http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT
  • rejectUnauthorized: 如为true则服务器证书会使用所给CA列表验证。如果验证失败则会触发error事件。验证过程发生于连接层,在HTTP请求发送之前。默认为true.
  • secureProtocol: 所用的SSL方法,比如TLSv1_method强制使用TLS version 1。可取值取决于您安装的OpenSSL,和定义于SSL_METHODS的常量。

要指定这些选项,使用一个自定义Agent

例如:

var options = {  hostname: 'encrypted.google.com',  port: 443,  path: '/',  method: 'GET',  key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),  cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem')};options.agent = new https.Agent(options);var req = https.request(options, function(res) {  ...}

或者不使用Agent.

例如:

var options = {  hostname: 'encrypted.google.com',  port: 443,  path: '/',  method: 'GET',  key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),  cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem'),  agent: false};var req = https.request(options, function(res) {  ...}

https.get(options, callback)

http.get()类似,不过是HTTPS版本的.

options可以是字符串对象. 如果options是字符串, 会自动使用url.parse()解析。

例如:

var https = require('https');https.get('https://encrypted.google.com/', function(res) {  console.log("statusCode: ", res.statusCode);  console.log("headers: ", res.headers);  res.on('data', function(d) {    process.stdout.write(d);  });}).on('error', function(e) {  console.error(e);});

类: https.Agent

HTTPS的Agent对象,和http.Agent类似。详情参见https.request()

https.globalAgent

所有HTTPS客户端请求的https.Agent全局实例。


稳定性: 4 - 冻结
  • {Object}

Node.js的console模块提供了一个简单的调试控制台。

Node.js控制台的作用是可以将输出字符打印到stdout(标准输出)和stderr(标准错误)。类似于大部分浏览器提供的console对象函数,Node也是输出到stdout和 stderr。

如果输出目标是终端或文件的时候,console函数是同步的(这是为了防止意外的退出而导致数据丢失),输出是管道的时候是异步的(防止阻塞时间太长)。

下面的例子里,stdout是非阻塞的,而stderr是阻塞的:

$ node script.js 2> error.log | tee info.log

平常使用过程中,只有发现大批量的数据时,才会考虑阻塞或非阻塞问题。

console.log([data][, ...])

输出到stdout并新起一行。和printf()类似,stdout可以传入多个参数,例如:

var count = 5;console.log('count: %d', count);// prints 'count: 5'

如果第一个字符里没有找到格式化的元素,util.inspect将会应用到各个参数,参见util.format()

console.info([data][, ...])

参见console.log

console.error([data][, ...])

参见console.log,不同的是打印到stderr。

console.warn([data][, ...])

参见console.error

console.dir(obj[, options])

obj使用util.inspect,并打印结果到stdout,而这个函数绕过inspect()options参数可能传入以下几种:

  • showHidden- 如果是true,将会展示对象的非枚举属性,默认是false

  • depth- inspect对象递归的次数,对于复杂对象的扫描非常有用。默认是2。想要严格递归,传入null

  • colors- 如果是true,输出会格式化为 ANSI 颜色代码。默认是false。颜色可以定制,下面会介绍。

console.time(label)

标记一个时间点。

console.timeEnd(label)

计时器结束的时候,记录输出,例如:

console.time('100-elements');for (var i = 0; i < 100; i++) {  ;}console.timeEnd('100-elements');// prints 100-elements: 262ms

console.trace(message[, ...])

输出当前位置的栈跟踪到stderr'Trace :'

console.assert(value[, message][, ...])

assert.ok()类似, 但是错误的输出格式为:util.format(message...)


稳定性: 5 - 锁定

本节介绍Node.js的模块系统。

Node.js有简单的模块加载系统。在Node.js模块系统中,每个文件都可以被当作单独的模块。下面例子里,foo.js对同一个文件夹里的circle.js模块进行加载。这是foo.js内容:

var circle = require('./circle.js');console.log( 'The area of a circle of radius 4 is '           + circle.area(4));

这是circle.js内容:

var PI = Math.PI;exports.area = function (r) {  return PI * r * r;};exports.circumference = function (r) {  return 2 * PI * r;};

circle.js模块输出了area()circumference()函数。想要给根模块添加函数和对象,你可以将他们添加到特定的exports对象。

加载到模块的变量是私有的,仿佛模块是包含在一个函数里。在这个例子里,PIcircle.js的私有变量。

如果你想模块里的根像一个函数一样的输出(比如,构造函数),或者你想输出一个完整对象,那就分派给module.exports,而不是exports

bar.js使用square模块,它输出了构造函数:

var square = require('./square.js');var mySquare = square(2);console.log('The area of my square is ' + mySquare.area());

square定义在square.js文件里:

// assigning to exports will not modify module, must use module.exportsmodule.exports = function(width) {  return {    area: function() {      return width * width;    }  };}

模块系统在require("module")模块里实现。

Cycles

环形调用require(),当返回时模块可能都没执行结束。

考虑以下场景:

a.js:

console.log('a starting');exports.done = false;var b = require('./b.js');console.log('in a, b.done = %j', b.done);exports.done = true;console.log('a done');

b.js:

console.log('b starting');exports.done = false;var a = require('./a.js');console.log('in b, a.done = %j', a.done);exports.done = true;console.log('b done');

main.js:

console.log('main starting');var a = require('./a.js');var b = require('./b.js');console.log('in main, a.done=%j, b.done=%j', a.done, b.done);

main.js加载a.jsa.js加载b.js。此时,b.js试着加载a.js。为了阻止循环调用,a.js输出对象的不完全拷贝返回给b.js模块。b.js会结束加载,并且它的exports对象提供给a.js模块。

main.js加载完两个模块时,它们都会结束。这个程序的输出如下:

$ node main.jsmain startinga startingb startingin b, a.done = falseb donein a, b.done = truea donein main, a.done=true, b.done=true

如果你的程序有环形模块依赖,需要保证是线性的。

核心模块

Node有很多模块编译成二进制。这些模块在本文档的其他地方有更详细的描述。

核心模块定义在Node的源代码lib/目录里。

require()总是会优先加载核心模块。例如,require('http')总是返回编译好的HTTP模块,而不管这个文件的名字。

文件模块

如果按照文件名没有找到模块,那么Node会试着加载添加了.js.json后缀的文件,如果还没好到,再试着加载添加了后缀.node的文件。

.js会解析为JavaScript的文本文件,.json会解析为JSON文本文件,.node会解析为编译过的插件模块,由dlopen负责加载。

模块的前缀'/'表示绝对路径。例如require('/home/marco/foo.js')将会加载 /home/marco/foo.js文件。

模块的前缀'./'表示相对于调用require()的路径。就是说,circle.js必须和foo.js在同一个目录里,require('./circle')才能找到。

文件前没有/./前缀,表示模块可能是core module,或者已经从node_modules文件夹里加载过了。

如果指定的路径不存在,require()将会抛出一个code属性为'MODULE_NOT_FOUND'的异常。

node_modules目录里加载

如传递给require()的模块不是一个本地模块,并且不以'/','../''./'开头,那么Node会从当前模块的父目录开始,尝试在它的node_modules文件夹里加载模块。

如果没有找到,那么会到父目录,直到到文件系统的根目录里找。

例如,如果'/home/ry/projects/foo.js'里的文件加载require('bar.js'),那么Node将会按照下面的顺序查找:

  • /home/ry/projects/node_modules/bar.js
  • /home/ry/node_modules/bar.js
  • /home/node_modules/bar.js
  • /node_modules/bar.js

这样允许程序独立,不会产生冲突。

可以请求指定的文件或分布子目录里的模块,在模块名后添加路径后缀。例如,require('example-module/path/to/file')会解决path/to/file相对于example-module的加载位置。路径后缀使用相同语法。

文件夹作为模块

可以把程序和库放到独立的文件夹里,并提供单一的入口指向他们。有三种方法可以将文件夹作为参数传给require()

第一个方法是,在文件夹的根创建一个package.json文件,它指定了main模块。package.json的例子如下:

{ "name" : "some-library",  "main" : "./lib/some-library.js" }

如果这是在./some-library里的文件夹,require('./some-library')将会试着加载./some-library/lib/some-library.js

如果文件夹里没有package.json文件,Node会试着加载index.jsindex.node文件。例如,如果上面的例子里没有package.json文件。那么 require('./some-library')将会试着加载:

  • ./some-library/index.js
  • ./some-library/index.node

缓存

模块第一次加载后会被被缓存。这就是说,每次调用require('foo')都会返回同一个对象,当然,必须每次都要解析到同一个文件。

多次调用require('foo')也许不会导致模块代码多次执行。这是很重要的特性,这样就可以返回"partially done"对象,允许加载过渡性的依赖关系,即使可能会引起环形调用。

如果你希望多次调用一个模块,那么就输出一个函数,然后调用这个函数。

模块换成预警

模块的缓存依赖于解析后的文件名。因此随着调用位置的不同,模块可能解析到不同的文件(例如,从node_modules文件夹加载)。如果解析为不同的文件,require('foo')可能会返回不同的对象。

module 对象

  • {Object}

在每个模块中,变量module是一个代表当前模块的对象的引用。为了方便,module.exports可以通过exports全局模块访问。module不是事实上的全局对象,而是每个模块内部的。

module.exports

  • {Object}

模块系统创建module.exports对象。很多人希望自己的模块是某个类的实例。因此,把将要导出的对象赋值给module.exports。注意,将想要的对象赋值给 exports,只是简单的将它绑定到本地exports变量,这可能并不是你想要的。

例如,假设我们有一个模块叫a.js

var EventEmitter = require('events').EventEmitter;module.exports = new EventEmitter();// Do some work, and after some time emit// the 'ready' event from the module itself.setTimeout(function() {  module.exports.emit('ready');}, 1000);

另一个文件可以写成如下的形式:

var a = require('./a');a.on('ready', function() {  console.log('module a is ready');});

注意:赋给module.exports必须马上执行,并且不能在回调中执行。

x.js:

setTimeout(function() {  module.exports = { a: "hello" };}, 0);

y.js:

var x = require('./x');console.log(x.a);

exports alias

exports变量在引用到module.exports的模块里可用。和其他变量一样,如果你给他赋一个新的值,它不再指向老的值。

为了展示这个特性,假设实现:require():

function require(...) {  // ...  function (module, exports) {    // Your module code here    exports = some_func;        // re-assigns exports, exports is no longer                                // a shortcut, and nothing is exported.    module.exports = some_func; // makes your module export 0  } (module, module.exports);  return module;}

如果你对exportsmodule.exports间的关系感到迷糊,那就只用module.exports就好。

module.require(id)

  • id {String}
  • 返回: {Object} 已经解析模块的module.exports

module.require方法提供了一种像require()一样从最初的模块加载一个模块的方法。

为了能这样做,你必须获得module对象的引用。require()返回module.exports,并且module是一个典型的只能在特定模块作用域内有效的变量,如果要使用它,就必须明确的导出。

module.id

  • {String}

模块的标识符。通常是完全解析的文件名。

module.filename

  • {String}

模块完全解析的文件名。

module.loaded

  • {Boolean}

模块是已经加载完毕,还是在加载中。

module.parent

  • {Module Object}

引入这个模块的模块。

module.children

  • {Array}

由这个模块引入的模块。

其他...

为了获取即将用require()加载的准确文件名,可以使用require.resolve()函数。

综上所述,下面用伪代码的高级算法形式演示了require.resolve的工作流程:

require(X) from module at path Y1. If X is a core module,   a. return the core module   b. STOP2. If X begins with './' or '/' or '../'   a. LOAD_AS_FILE(Y + X)   b. LOAD_AS_DIRECTORY(Y + X)3. LOAD_NODE_MODULES(X, dirname(Y))4. THROW "not found"LOAD_AS_FILE(X)1. If X is a file, load X as JavaScript text.  STOP2. If X.js is a file, load X.js as JavaScript text.  STOP3. If X.json is a file, parse X.json to a JavaScript Object.  STOP4. If X.node is a file, load X.node as binary addon.  STOPLOAD_AS_DIRECTORY(X)1. If X/package.json is a file,   a. Parse X/package.json, and look for "main" field.   b. let M = X + (json main field)   c. LOAD_AS_FILE(M)2. If X/index.js is a file, load X/index.js as JavaScript text.  STOP3. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP4. If X/index.node is a file, load X/index.node as binary addon.  STOPLOAD_NODE_MODULES(X, START)1. let DIRS=NODE_MODULES_PATHS(START)2. for each DIR in DIRS:   a. LOAD_AS_FILE(DIR/X)   b. LOAD_AS_DIRECTORY(DIR/X)NODE_MODULES_PATHS(START)1. let PARTS = path split(START)2. let I = count of PARTS - 13. let DIRS = []4. while I >= 0,   a. if PARTS[I] = "node_modules" CONTINUE   c. DIR = path join(PARTS[0 .. I] + "node_modules")   b. DIRS = DIRS + DIR   c. let I = I - 15. return DIRS

从全局文件夹加载

如果环境变量NODE_PATH设置为冒号分割的绝对路径列表,并且在模块在其他地方没有找到,Node将会搜索这些路径。(注意,在Windows系统中,NODE_PATH用分号分割 )。

另外,Node将会搜索这些路径。

  • 1:$HOME/.node_modules
  • 2:$HOME/.node_libraries
  • 3:$PREFIX/lib/node

$HOME是用户的home文件夹,$PREFIX是Node里配置的node_prefix

这大多是历史原因照成的。强烈建议将所以来的模块放到node_modules文件夹里。这样加载会更快。

访问主模块

当Node运行一个文件时,require.main就会设置为它的module。也就是说你可以通过测试判断文件是否被直接运行。

require.main === module

对于foo.js文件。 如果直接运行node foo.js,返回true,如果通过require('./foo')是间接运行。

因为module提供了filename属性(通常等于__filename),程序的入口点可以通过检查require.main.filename来获得。

附录: 包管理技巧

Node的require()函数语义定义的足够通用,它能支持各种常规目录结构。诸如dpkg,rpmnpm包管理程序,不用修改就可以从Node模块构建本地包。

下面我们介绍一个可行的目录结构:

假设我们有一个/usr/lib/node/<some-package>/<some-version>文件夹,它包含指定版本的包内容。

一个包可以依赖于其他包。为了安装包foo,可能需要安装特定版本的bar包。bar包可能有自己的包依赖,某些条件下,依赖关系可能会发生冲突或形成循环。

因为Node会查找他所加载的模块的realpath(也就是说会解析符号链接),然后按照上文描述的方式在node_modules目录中寻找依赖关系,这种情形跟以下体系结构非常相像:

  • /usr/lib/node/foo/1.2.3/ - foo包,version 1.2.3。
  • /usr/lib/node/bar/4.3.2/ - foo依赖的bar包内容。
  • /usr/lib/node/foo/1.2.3/node_modules/bar- 指向/usr/lib/node/bar/4.3.2/的符号链接。
  • /usr/lib/node/bar/4.3.2/node_modules/*- 指向bar包所依赖的包的符号链接。

因此,即使存在循环依赖或依赖冲突,每个模块还可以获得他所依赖的包得可用版本。

foo包里的代码调用foo ,将会获得符号链接/usr/lib/node/foo/1.2.3/node_modules/bar指向的版本。然后,当bar包中的代码调用 require('queue'),将会获得符号链接/usr/lib/node/bar/4.3.2/node_modules/quux指向的版本。

另外,为了让模块搜索更快些,不要将包直接放在/usr/lib/node目录中,而是将它们放在/usr/lib/node_modules/<name>/<version>目录中。这样在依赖的包找不到的情况下,就不会一直寻找/usr/node_modules目录或/node_modules目录了。基于调用require()的文件所在真实路径,因此包本身可以放在任何位置。

为了让Node模块对Node REPL可用,可能需要将/usr/lib/node_modules文件夹路径添加到环境变量$NODE_PATH。由于模块查找$NODE_PATH文件夹都是相对路径,因此包可以放到任何位置。


稳定性: 2 - 不稳定; 正在讨论未来版本的 API 改进,会尽量减少重大变化。详见后文。

顾名思义,Node.js加密模块允许你使用加密的功能,Node.js加密模块通过使用require('crypto')来访问。

Node.js加密模块提供了HTTP或HTTPS连接过程中封装安全凭证的方法。

Node.js加密模块还提供了OpenSSL的哈希,hmac、加密(cipher)、解密(decipher)、签名(sign)和验证(verify)方法的封装。

crypto.setEngine(engine[, flags])

为某些/所有OpenSSL函数加载并设置引擎(根据参数flags来设置)。

engine可能是id,或者是指向引擎共享库的路径。

flags是可选参数,默认值是ENGINE_METHOD_ALL,它可以是以下一个或多个参数的组合(在constants里定义):

  • ENGINE_METHOD_RSA
  • ENGINE_METHOD_DSA
  • ENGINE_METHOD_DH
  • ENGINE_METHOD_RAND
  • ENGINE_METHOD_ECDH
  • ENGINE_METHOD_ECDSA
  • ENGINE_METHOD_CIPHERS
  • ENGINE_METHOD_DIGESTS
  • ENGINE_METHOD_STORE
  • ENGINE_METHOD_PKEY_METH
  • ENGINE_METHOD_PKEY_ASN1_METH
  • ENGINE_METHOD_ALL
  • ENGINE_METHOD_NONE

crypto.getCiphers()

返回支持的加密算法名数组。

例如:

var ciphers = crypto.getCiphers();console.log(ciphers); // ['AES-128-CBC', 'AES-128-CBC-HMAC-SHA1', ...]

crypto.getHashes()

返回支持的哈希算法名数组。

例如:

var hashes = crypto.getHashes();console.log(hashes); // ['sha', 'sha1', 'sha1WithRSAEncryption', ...]

crypto.createCredentials(details)

稳定性: 0 - 抛弃。用 [tls.createSecureContext][] 替换

根据参数details,创建一个加密凭证对象。参数为字典,key包括:

  • pfx: 字符串或者buffer对象,表示经PFX或PKCS12编码产生的私钥、证书以及CA证书
  • key: 进过 PEM 编码的私钥
  • passphrase: 私钥或pfx的密码
  • cert: PEM编码的证书
  • ca: 字符串或字符串数组,PEM编码的可信任的CA证书。
  • crl: 字符串或字符串数组,PEM编码的CRLs(证书吊销列表Certificate Revocation List)。
  • ciphers: 字符串,使用或者排除的加密算法。参见http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT

如果没有指定'ca',Node.js将会使用下面列表中的CAhttp://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt

crypto.createHash(algorithm)

创建并返回一个哈希对象,使用指定的算法来生成哈希摘要。

参数algorithm取决于平台的OpenSSL版本所支持的算法。例如,'sha1''md5''sha256''sha512'等等。在最近的版本中,openssllist-message-digest-algorithms会显示所有算法。

例如: 这个程序会计算文件的sha1的和。

var filename = process.argv[2];var crypto = require('crypto');var fs = require('fs');var shasum = crypto.createHash('sha1');var s = fs.ReadStream(filename);s.on('data', function(d) {  shasum.update(d);});s.on('end', function() {  var d = shasum.digest('hex');  console.log(d + '  ' + filename);});

类:Hash

Hase用来生成数据的哈希值。

它是可读写的流stream。写入的数据来用计算哈希值。当写入流结束后,使用read()方法来获取计算后的哈希值。也支持旧的updatedigest方法。

通过crypto.createHash返回。

hash.update(data[, input_encoding])

根据data来更新哈希内容,编码方式根据input_encoding来定,有'utf8''ascii''binary'。如果没有传入值,默认编码方式是'binary'。如果 dataBuffer,则input_encoding将会被忽略。

因为它是流式数据,所以可以使用不同的数据调用很多次。

hash.digest([encoding])

计算传入的数据的哈希摘要。

encoding可以是'hex''binary''base64',如果没有指定encoding,将返回buffer。
注意:调用digest()后不能再用hash对象。

crypto.createHmac(algorithm, key)

创建并返回一个hmac对象,用指定的算法和秘钥生成hmac图谱。

它是可读写的流stream。写入的数据来用计算hmac。当写入流结束后,使用read()方法来获取计算后的值。也支持旧的updatedigest方法。

参数algorithm取决于平台上OpenSSL版本所支持的算法,参见前面的createHash。key是hmac算法中用的key。

类:Hmac

用来创建hmac加密图谱。

通过crypto.createHmac返回。

hmac.update(data)

根据data更新hmac对象。因为它是流式数据,所以可以使用新数据调用多次。

hmac.digest([encoding])

计算传入数据的hmac值。encoding可以是'hex''binary''base64',如果没有指定encoding,将返回buffer。

注意:调用digest()后不能再用hmac对象。

crypto.createCipher(algorithm, password)

使用传入的算法和秘钥来生成并返回加密对象。

algorithm取决于OpenSSL,例如'aes192'等。最近发布的版本中,openssl list-cipher-algorithms将会展示可用的加密算法。password用来派生key 和IV,它必须是一个'binary'编码的字符串或者一个buffer

它是可读写的stream流。写入的数据来用计算hmac。当写入流结束后,使用read()方法来获取计算后的值。也支持老的updatedigest方法。

注意,OpenSSL函数EVP_BytesToKey摘要算法如果是一次迭代(one iteration),无需盐值(no salt)的MD5时,createCipher为它派生秘钥。缺少盐值使得字典攻击,相同的密码总是生成相同的key,低迭代次数和非加密的哈希算法,使得密码测试非常迅速。

OpenSSL推荐使用pbkdf2来替换EVP_BytesToKey,推荐使用crypto.pbkdf2来派生key和iv ,推荐使用createCipheriv()来创建加密流。

crypto.createCipheriv(algorithm, key, iv)

创建并返回一个加密对象,用指定的算法,key和iv。

algorithm参数和createCipher()一致。key在算法中用到。iv是一个initialization vector.

keyiv必须是'binary'的编码字符串或buffers.

类: Cipher

加密数据的类。.

通过crypto.createCiphercrypto.createCipheriv返回。

它是可读写的stream流。写入的数据来用计算hmac。当写入流结束后,使用read()方法来获取计算后的值。也支持旧的updatedigest方法。

cipher.update(data[, input_encoding][, output_encoding])

根据data来更新哈希内容,编码方式根据input_encoding来定,有'utf8''ascii'或者'binary'。如果没有传入值,默认编码方式是'binary'。如果dataBufferinput_encoding将会被忽略。

output_encoding指定了输出的加密数据的编码格式,它可用是'binary''base64''hex'。如果没有提供编码,将返回buffer。

返回加密后的内容,因为它是流式数据,所以可以使用不同的数据调用很多次。

cipher.final([output_encoding])

返回加密后的内容,编码方式是由output_encoding指定,可以是'binary''base64''hex'。如果没有传入值,将返回buffer。

注意:cipher对象不能在final()方法之后调用。

cipher.setAutoPadding(auto_padding=true)

你可以禁用输入数据自动填充到块大小的功能。如果auto_padding是false, 那么输入数据的长度必须是加密器块大小的整倍数,否则final会失败。这对非标准的填充很有用,例如使用0x0而不是PKCS的填充。这个函数必须在cipher.final之前调用。

cipher.getAuthTag()

加密认证模式(目前支持:GCM),这个方法返回经过计算的认证标志Buffer。必须使用final方法完全加密后调用。

cipher.setAAD(buffer)

加密认证模式(目前支持:GCM),这个方法设置附加认证数据( AAD )。

crypto.createDecipher(algorithm, password)

根据传入的算法和密钥,创建并返回一个解密对象。这是createCipher()的镜像。

crypto.createDecipheriv(algorithm, key, iv)

根据传入的算法,密钥和iv,创建并返回一个解密对象。这是createCipheriv()的镜像。

类:Decipher

解密数据类。

通过crypto.createDeciphercrypto.createDecipheriv返回。

解密对象是可读写的streams流。用写入的加密数据生成可读的纯文本数据。也支持老的updatedigest方法。

decipher.update(data[, input_encoding][, output_encoding])

使用参数data更新需要解密的内容,其编码方式是'binary''base64''hex'。如果没有指定编码方式,则把data当成buffer对象。

如果dataBuffer,则忽略input_encoding参数。

参数output_decoding指定返回文本的格式,是'binary''ascii''utf8'之一。如果没有提供编码格式,则返回buffer。

decipher.final([output_encoding])

返回剩余的解密过的内容,参数output_encoding'binary''ascii''utf8',如果没有指定编码方式,返回buffer。

注意:decipher对象不能在final()方法之后使用。

decipher.setAutoPadding(auto_padding=true)

如果加密的数据是非标准块,可以禁止其自动填充,防止decipher.final检查并移除。仅在输入数据长度是加密块长度的整数倍的时才有效。你必须在 decipher.update前调用。

decipher.setAuthTag(buffer)

对于加密认证模式(目前支持:GCM),必须用这个方法来传递接收到的认证标志。如果没有提供标志,或者密文被篡改,将会抛出final标志,认证失败,密文会被抛弃。

decipher.setAAD(buffer)

对于加密认证模式(目前支持:GCM),用这个方法设置附加认证数据( AAD )。

crypto.createSign(algorithm)

根据传入的算法创建并返回一个签名数据。 OpenSSL的最近版本里,openssl list-public-key-algorithms会列出所有算法,比如'RSA-SHA256'

类:Sign

生成数字签名的类。

通过crypto.createSign返回。

签名对象是可读写的streams流。可写数据用来生成签名。当所有的数据写完,sign签名方法会返回签名。也支持老的updatedigest方法。

sign.update(data)

用参数data来更新签名对象。因为是流式数据,它可以被多次调用。

sign.sign(private_key[, output_format])

根据传送给sign的数据来计算电子签名。

private_key可以是一个对象或者字符串。如果是字符串,将会被当做没有密码的key。

private_key:

  • key: 包含 PEM 编码的私钥
  • passphrase: 私钥的密码

返回值output_format包含数字签名, 格式是'binary''hex''base64'之一。如果没有指定encoding,将返回buffer。

注意:sign对象不能在sign()方法之后调用。

crypto.createVerify(algorithm)

根据传入的算法,创建并返回验证对象。是签名对象(signing object)的镜像。

类: Verify

用来验证签名的类。

通过crypto.createVerify返回。

是可写streams流。可写数据用来验证签名。一旦所有数据写完后,如签名正确verify方法会返回true

也支持老的update方法。

verifier.update(data)

用参数data来更新验证对象。因为是流式数据,它可以被多次调用。

verifier.verify(object, signature[, signature_format])

使用objectsignature验证签名数据。参数object是包含了PEM编码对象的字符串,它可以是RSA公钥,DSA公钥,或X.509证书。signature是之前计算出来的数字签名。signature_format可以是'binary''hex''base64'之一,如果没有指定编码方式 ,则默认是buffer对象。

根据数据和公钥验证签名有效性,来返回true或false。

注意:verifier对象不能在verify()方法之后调用。

crypto.createDiffieHellman(prime_length[, generator])

创建一个Diffie-Hellman密钥交换(Diffie-Hellman key exchange)对象,并根据给定的位长度生成一个质数。如果没有指定参数generator,默认为2

crypto.createDiffieHellman(prime[, prime_encoding][, generator][, generator_encoding])

使用传入的primegenerator创建Diffie-Hellman秘钥交互对象。

generator可以是数字,字符串或Buffer。

如果没有指定generator,使用2.

prime_encodinggenerator_encoding可以是'binary''hex''base64'

如果没有指定prime_encoding, 则Buffer为prime

如果没有指定generator_encoding ,则Buffer为generator

类:DiffieHellman

创建Diffie-Hellman秘钥交换的类。

通过crypto.createDiffieHellman返回。

diffieHellman.verifyError

在初始化的时候,如果有警告或错误,将会反应到这。它是以下值(定义在constants模块):

  • DH_CHECK_P_NOT_SAFE_PRIME
  • DH_CHECK_P_NOT_PRIME
  • DH_UNABLE_TO_CHECK_GENERATOR
  • DH_NOT_SUITABLE_GENERATOR

diffieHellman.generateKeys([encoding])

生成秘钥和公钥,并返回指定格式的公钥。这个值必须传给其他部分。编码方式:'binary''hex''base64'。如果没有指定编码方式,将返回buffer。

diffieHellman.computeSecret(other_public_key[, input_encoding][, output_encoding])

使用other_public_key作为第三方公钥来计算并返回共享秘密(shared secret)。秘钥用input_encoding编码。编码方式为:'binary''hex''base64'。如果没有指定编码方式 ,默认为buffer。

如果没有指定返回编码方式,将返回buffer。

diffieHellman.getPrime([encoding])

用参数encoding指明的编码方式返回Diffie-Hellman质数,编码方式为: 'binary''hex''base64'。如果没有指定编码方式,将返回buffer。

diffieHellman.getGenerator([encoding])

用参数encoding指明的编码方式返回Diffie-Hellman生成器,编码方式为: 'binary''hex''base64'。如果没有指定编码方式 ,将返回buffer。

diffieHellman.getPublicKey([encoding])

用参数encoding指明的编码方式返回Diffie-Hellman公钥,编码方式为: 'binary''hex', 或'base64'。如果没有指定编码方式 ,将返回buffer。

diffieHellman.getPrivateKey([encoding])

用参数encoding指明的编码方式返回Diffie-Hellman私钥,编码方式为: 'binary''hex''base64'。如果没有指定编码方式 ,将返回buffer。

diffieHellman.setPublicKey(public_key[, encoding])

设置Diffie-Hellman的公钥,编码方式为: 'binary''hex''base64',如果没有指定编码方式 ,默认为buffer。

diffieHellman.setPrivateKey(private_key[, encoding])

设置Diffie-Hellman的私钥,编码方式为: 'binary''hex''base64',如果没有指定编码方式 ,默认为buffer。

crypto.getDiffieHellman(group_name)

创建一个预定义的Diffie-Hellman秘钥交换对象。支持的组: 'modp1''modp2''modp5'(定义于RFC 2412),并且'modp14''modp15''modp16''modp17''modp18'(定义于RFC 3526)。返回对象模仿了上述创建的crypto.createDiffieHellman()对象,但是不允许修改秘钥交换(例如,diffieHellman.setPublicKey())。使用这套流程的好处是,双方不需要生成或交换组组余数,节省了计算和通讯时间。

例如 (获取一个共享秘密):

var crypto = require('crypto');var alice = crypto.getDiffieHellman('modp5');var bob = crypto.getDiffieHellman('modp5');alice.generateKeys();bob.generateKeys();var alice_secret = alice.computeSecret(bob.getPublicKey(), null, 'hex');var bob_secret = bob.computeSecret(alice.getPublicKey(), null, 'hex');/* alice_secret and bob_secret should be the same */console.log(alice_secret == bob_secret);

crypto.createECDH(curve_name)

使用传入的参数curve_name,创建一个Elliptic Curve (EC) Diffie-Hellman秘钥交换对象。

类:ECDH

这个类用来创建EC Diffie-Hellman秘钥交换。

通过crypto.createECDH返回。

ECDH.generateKeys([encoding[, format]])

生成EC Diffie-Hellman的秘钥和公钥,并返回指定格式和编码的公钥,它会传递给第三方。

参数format'compressed'、 'uncompressed''hybrid'。如果没有指定,将返回'uncompressed'格式.

参数encoding'binary''hex''base64'。如果没有指定编码方式,将返回buffer。

ECDH.computeSecret(other_public_key[, input_encoding][, output_encoding])

other_public_key作为第三方公钥计算共享秘密,并返回。秘钥会以input_encoding来解读。编码是:'binary''hex''base64'。如果没有指定编码方式,默认为buffer。

如果没有指定编码方式,将返回buffer。

ECDH.getPublicKey([encoding[, format]])

用参数encoding指明的编码方式返回EC Diffie-Hellman公钥,编码方式为: 'compressed''uncompressed''hybrid'。如果没有指定编码方式 ,将返回'uncompressed'

编码是:'binary''hex''base64'。如果没有指定编码方式 ,默认为buffer。

ECDH.getPrivateKey([encoding])

用参数encoding指明的编码方式返回EC Diffie-Hellman私钥,编码是:'binary''hex''base64'。如果没有指定编码方式 ,默认为buffer。

ECDH.setPublicKey(public_key[, encoding])

设置EC Diffie-Hellman的公钥,编码方式为: 'binary''hex''base64',如果没有指定编码方式,默认为buffer。

ECDH.setPrivateKey(private_key[, encoding])

设置EC Diffie-Hellman的私钥,编码方式为: 'binary''hex''base64',如果没有指定编码方式,默认为buffer。

例如 (包含一个共享秘密):

var crypto = require('crypto');var alice = crypto.createECDH('secp256k1');var bob = crypto.createECDH('secp256k1');alice.generateKeys();bob.generateKeys();var alice_secret = alice.computeSecret(bob.getPublicKey(), null, 'hex');var bob_secret = bob.computeSecret(alice.getPublicKey(), null, 'hex');/* alice_secret and bob_secret should be the same */console.log(alice_secret == bob_secret);

crypto.pbkdf2(password, salt, iterations, keylen[, digest], callback)

异步PBKDF2提供了一个伪随机函数HMAC-SHA1,根据给定密码的长度,salt和iterations来得出一个密钥。回调函数得到两个参数 (err, derivedKey)。

例如:

crypto.pbkdf2('secret', 'salt', 4096, 512, 'sha256', function(err, key) {  if (err)    throw err;  console.log(key.toString('hex'));  // 'c5e478d...1469e50'});

crypto.getHashes()里有支持的摘要函数列表。

crypto.pbkdf2Sync(password, salt, iterations, keylen[, digest])

异步PBKDF2函数, 返回derivedKey或抛出错误。

crypto.randomBytes(size[, callback])

生成一个密码强度随机的数据:

// asynccrypto.randomBytes(256, function(ex, buf) {  if (ex) throw ex;  console.log('Have %d bytes of random data: %s', buf.length, buf);});// synctry {  var buf = crypto.randomBytes(256);  console.log('Have %d bytes of random data: %s', buf.length, buf);} catch (ex) {  // handle error  // most likely, entropy sources are drained}

注意:如果没有足够积累的熵来生成随机强度的密码,将会抛出错误,或调用回调函数返回错误。换句话说,没有回调函数的crypto.randomBytes不会阻塞,即使耗尽所有的熵。

crypto.pseudoRandomBytes(size[, callback])

生成非密码学强度的伪随机数据。如果数据足够长会返回一个唯一数据,但是这个数可能是可以预期的。因此,当不可预期很重要的时候,不要用这个函数。例如,在生成加密的秘钥时。

用法和crypto.randomBytes相同。

类: Certificate

这个类和签过名的公钥打交道。最重要的场景是处理<keygen>元素,http://www.openssl.org/docs/apps/spkac.html

通过crypto.Certificate返回.

Certificate.verifySpkac(spkac)

根据SPKAC返回true或false。

Certificate.exportChallenge(spkac)

根据提供的SPKAC,返回加密的公钥。

Certificate.exportPublicKey(spkac)

输出和SPKAC关联的编码challenge。

crypto.publicEncrypt(public_key, buffer)

使用public_key加密buffer。目前仅支持RSA。

public_key可以是对象或字符串。如果public_key是一个字符串,将会当做没有密码的key,并会用RSA_PKCS1_OAEP_PADDING

public_key:

  • key: 包含有PEM编码的私钥。
  • padding: 填充值,如下
    • constants.RSA_NO_PADDING
    • constants.RSA_PKCS1_PADDING
    • constants.RSA_PKCS1_OAEP_PADDING

注意:所有的填充值定义在constants模块.

crypto.privateDecrypt(private_key, buffer)

使用private_key来解密buffer.

private_key:

  • key: 包含有 PEM 编码的私钥
  • passphrase: 私钥的密码
  • padding: 填充值,如下:
    • constants.RSA_NO_PADDING
    • constants.RSA_PKCS1_PADDING
    • constants.RSA_PKCS1_OAEP_PADDING

注意:所有的填充值定义于constants模块.

crypto.DEFAULT_ENCODING

函数所用的编码方式可以是字符串或buffer ,默认值是'buffer'。这是为了加密模块兼容默认'binary'为编码方式的遗留程序。

注意:新程序希望用buffer对象,所以这是暂时手段。

Recent API Changes

在统一的流API概念出现前,在引入Buffer对象来处理二进制数据之前,Crypto模块就已经添加到Node。

因此,流相关的类里没有其他的Node类里的典型方法,并且很多方法接收并返回二级制编码的字符串,而不是Buffers。在最近的版本中,这些函数改成默认使用 Buffers。

对于一些场景来说这是重大变化。

例如,如果你使用默认参数给签名类,将结果返回给认证类,中间没有验证数据,程序会正常工作。之前你会得到二进制编码的字符串,并传递给验证类,现在则是 Buffer。

如果你之前使用的字符串数据在Buffers对象不能正常工作(比如,连接数据,并存储在数据库里 )。或者你传递了二进制字符串给加密函数,但是没有指定编码方式,现在就需要提供编码参数。如果想切换回原来的风格,将crypto.DEFAULT_ENCODING设置为'binary'。注意,新的程序希望是buffers,所以之前的方法只能作为临时的办法。


稳定性: 2 - 不稳定

流用于处理Node.js中的流数据的抽象接口,在Node里被不同的对象实现。例如,对HTTP服务器的请求是流,process.stdout 是流。

流是可读的,可写的,或者是可读写的,所有的流是EventEmitter的实例。

Node.js访问流模块的方法如下所示:

const stream = require('stream');

你可以通过require('stream')加载Stream基类。其中包括了Readable流、Writable流、Duplex流和Transform流的基类。

本文将分为3个部分进行介绍。

第一个部分解释了在你的程序中使用流时候需要了解的内容。如果你不用实现流式API,可以只看这个部分。

如果你想实现你自己的流,第二个部分解释了这部分API。这些API让你的实现更加简单。

第三个部分深入的解释了流是如何工作的,包括一些内部机制和函数,这些内容不要改动,除非你明确知道你要做什么。

面向流消费者的API

流可以是可读(Readable),可写(Writable),或者是可读可写的(Duplex,双工)。

所有的流都是事件分发器(EventEmitters),但是也有自己的方法和属性,这取决于他它们是可读(Readable),可写(Writable),或者兼具两者(Duplex,双工)的。

如果流是可读写的,则它实现了下面的所有方法和事件。因此,这个部分API完全阐述了DuplexTransform流,即便他们的实现有所不同。

没有必要为了消费流而在你的程序里实现流的接口。如果你正在你的程序里实现流接口,请同时参考下面的流实现程序API

基本所有的Node程序,无论多简单,都会使用到流。以下是一个使用流的例子:

javascriptvar http = require('http');var server = http.createServer(function (req, res) {  // req is an http.IncomingMessage, which is 可读流(Readable stream)  // res is an http.ServerResponse, which is a Writable Stream  var body = '';  // we want to get the data as utf8 strings  // If you don't set an encoding, then you'll get Buffer objects  req.setEncoding('utf8');  // 可读流(Readable stream) emit 'data' 事件 once a 监听器(listener) is added  req.on('data', function (chunk) {    body += chunk;  });  // the end 事件 tells you that you have entire body  req.on('end', function () {    try {      var data = JSON.parse(body);    } catch (er) {      // uh oh!  bad json!      res.statusCode = 400;      return res.end('error: ' + er.message);    }    // write back something interesting to the user:    res.write(typeof data);    res.end();  });});server.listen(1337);// $ curl localhost:1337 -d '{}'// object// $ curl localhost:1337 -d '"foo"'// string// $ curl localhost:1337 -d 'not json'// error: Unexpected token o

类: stream.Readable

可读流(Readable stream)接口是对你正在读取的数据的来源的抽象。换句话说,数据来来自

可读流(Readable stream)不会分发数据,直到你表明准备就绪。

可读流(Readable stream) 有2种模式: 流动模式(flowing mode)暂停模式(paused mode)。流动模式(flowing mode)时,尽快的从底层系统读取数据并提供给你的程序。暂停模式(paused mode)时,你必须明确的调用stream.read()来读取数据。暂停模式(paused mode)是默认模式。

注意: 如果没有绑定数据处理函数,并且没有pipe()目标,流会切换到流动模式(flowing mode),并且数据会丢失。

可以通过下面几个方法,将流切换到流动模式(flowing mode)。

  • 添加一个['data' 事件][]事件处理器来监听数据.
  • 调用resume()方法来明确的开启数据流。
  • 调用pipe()方法来发送数据给Writable.

可以通过以下方法来切换到暂停模式(paused mode):

  • 如果没有“导流(pipe)”目标,调用pause()方法.
  • 如果有“导流(pipe)”目标,移除所有的['data'事件][]处理函数,调用unpipe()方法移除所有的“导流(pipe)”目标。

注意:为了向后兼容考虑, 移除'data'事件监听器并不会自动暂停流。同样的,当有导流目标时,调用pause()并不能保证流在那些目标排空后,请求更多数据时保持暂停状态。

可读流(Readable stream)例子包括:

事件: 'readable'

当一个数据块可以从流中读出,将会触发'readable'事件.`

某些情况下, 如果没有准备好,监听一个'readable'事件将会导致一些数据从底层系统读取到内部缓存。

javascriptvar readble = getReadableStreamSomehow();readable.on('readable', function() {  // there is some data to read now});

一旦内部缓存排空,一旦有更多数据将会再次触发readable事件。

事件: 'data'

  • chunk {Buffer | String} 数据块

绑定一个data事件的监听器(listener)到一个未明确暂停的流,会将流切换到流动模式。数据会尽额能的传递。

如果你像尽快的从流中获取数据,以下的方法是最快的:

javascriptvar readable = getReadableStreamSomehow();readable.on('data', function(chunk) {  console.log('got %d bytes of data', chunk.length);});

事件: 'end'

如果没有更多的可读数据,将会触发这个事件。

注意:只有数据已经被完全消费,end事件才会触发。可以通过切换到流动模式(flowing mode)来实现,或者通过调用重复调用read()获取数据,直到结束。

javascript    var readable = getReadableStreamSomehow();    readable.on('data', function(chunk) {        console.log('got %d bytes of data', chunk.length);    });    readable.on('end', function() {        console.log('there will be no more data.');    });  

事件: 'close'

当底层资源(例如源头的文件描述符)关闭时触发。并不是所有流都会触发这个事件。

事件: 'error'

  • {Error Object}

当接收数据时发生错误触发。

readable.read([size])

  • size {Number} 可选参数, 需要读入的数据量
  • 返回 {String | Buffer | null}

read()方法从内部缓存中拉取数据。如果没有可用数据,将会返回null

如果传了size参数,将会返回相当字节的数据。如果size不可用,将会返回null

如果你没有指定size参数。将会返回内部缓存的所有数据。

这个方法仅能再暂停模式(paused mode)里调用。 流动模式(flowing mode)下这个方法会被自动调用直到内存缓存排空。

javascriptvar readable = getReadableStreamSomehow();readable.on('readable', function() {  var chunk;  while (null !== (chunk = readable.read())) {    console.log('got %d bytes of data', chunk.length);  }});

如果这个方法返回一个数据块, 它同时也会触发['data'事件][].

readable.setEncoding(encoding)

  • encoding {String} 要使用的编码.
  • 返回:this

调用此函数会使得流返回指定编码的字符串,而不是Buffer对象。例如,如果你调用readable.setEncoding('utf8'),输出数据将会是UTF-8编码,并且返回字符串。如果你调用readable.setEncoding('hex'),将会返回2进制编码的数据。

该方法能正确处理多字节字符。如果不想这么做,仅简单的直接拉取缓存并调buf.toString(encoding),可能会导致字节错位。因此,如果你想以字符串读取数据,请使用下述的方法:

javascriptvar readable = getReadableStreamSomehow();readable.setEncoding('utf8');readable.on('data', function(chunk) {  assert.equal(typeof chunk, 'string');  console.log('got %d characters of string data', chunk.length);});

readable.resume()

  • 返回:this

这个方法让可读流(Readable stream)继续触发data事件.

这个方法会将流切换到流动模式(flowing mode)。 如果你不想从流中消费数据,而想得到end事件,可以调用readable.resume()来打开数据流,如下所示:

javascriptvar readable = getReadableStreamSomehow();readable.resume();readable.on('end', function(chunk) {  console.log('got to the end, but did not read anything');});

readable.pause()

  • 返回:this

这个方法会使得流动模式(flowing mode)的流停止触发data事件,切换到流动模式(flowing mode)。并让后续可用数据留在内部缓冲区中。

javascriptvar readable = getReadableStreamSomehow();readable.on('data', function(chunk) {  console.log('got %d bytes of data', chunk.length);  readable.pause();  console.log('there will be no more data for 1 second');  setTimeout(function() {    console.log('now data will start flowing again');    readable.resume();  }, 1000);});

readable.isPaused()

  • 返回:Boolean

这个方法返回readable是否被客户端代码明确的暂停(调用readable.pause())。

var readable = new stream.Readablereadable.isPaused() // === falsereadable.pause()readable.isPaused() // === truereadable.resume()readable.isPaused() // === false

readable.pipe(destination[, options])

  • destination {Writable Stream} 写入数据的目标
  • options {Object} 导流(pipe)选项
    • end {Boolean} 读取到结束符时,结束写入者。默认 = true

这个方法从可读流(Readable stream)拉取所有数据,并将数据写入到提供的目标中。自动管理流量,这样目标不会快速的可读流(Readable stream)淹没。

可以导流到多个目标。

javascriptvar readable = getReadableStreamSomehow();var writable = fs.createWriteStream('file.txt');// All the data from readable goes into 'file.txt'readable.pipe(writable);

这个函数返回目标流, 因此你可以建立导流链:

javascriptvar r = fs.createReadStream('file.txt');var z = zlib.createGzip();var w = fs.createWriteStream('file.txt.gz');r.pipe(z).pipe(w);

例如:模拟Unix的cat命令:

javascriptprocess.stdin.pipe(process.stdout);

默认情况下,当源数据流触发end的时候调用end(),所以destination不可再写。传{ end:false }作为options,可以保持目标流打开状态。

这会让writer保持打开状态,可以在最后写入"Goodbye":

javascriptreader.pipe(writer, { end: false });reader.on('end', function() {  writer.end('Goodbye
');});

注意:process.stderrprocess.stdout直到进程结束才会关闭,无论是否指定它们。

readable.unpipe([destination])

  • destination {Writable Stream} 可选,指定解除导流的流

这个方法会解除之前调用pipe() 设置的钩子(pipe())。

如果没有指定destination,则所有的导流(pipe)都会被移除。

如果指定了destination,但是没有建立如果没有指定destination,则什么事情都不会发生。

javascriptvar readable = getReadableStreamSomehow();var writable = fs.createWriteStream('file.txt');// All the data from readable goes into 'file.txt',// but only for the first secondreadable.pipe(writable);setTimeout(function() {  console.log('stop writing to file.txt');  readable.unpipe(writable);  console.log('manually close the file stream');  writable.end();}, 1000);

readable.unshift(chunk)

  • chunk {Buffer | String} 数据块插入到读队列中

这个方法很有用,当一个流正被一个解析器消费,解析器可能需要将某些刚拉取出的数据“逆消费”,返回到原来的源,以便流能将它传递给其它消费者。

如果你在程序中必须经常调用stream.unshift(chunk) ,那你可以考虑实现Transform来替换(参见下文API for Stream Implementors)。

javascript// Pull off a header delimited by 

// use unshift() if we get too much// Call the callback with (error, header, stream)var StringDecoder = require('string_decoder').StringDecoder;function parseHeader(stream, callback) {  stream.on('error', callback);  stream.on('readable', onReadable);  var decoder = new StringDecoder('utf8');  var header = '';  function onReadable() {    var chunk;    while (null !== (chunk = stream.read())) {      var str = decoder.write(chunk);      if (str.match(/

/)) {        // found the header boundary        var split = str.split(/

/);        header += split.shift();        var remaining = split.join('

');        var buf = new Buffer(remaining, 'utf8');        if (buf.length)          stream.unshift(buf);        stream.removeListener('error', callback);        stream.removeListener('readable', onReadable);        // now the body of the message can be read from the stream.        callback(null, header, stream);      } else {        // still reading the header.        header += str;      }    }  }}

readable.wrap(stream)

  • stream {Stream} 一个旧式的可读流(Readable stream)

v0.10版本之前的Node流并未实现现在所有流的API(更多信息详见下文“兼容性”部分)。

如果你使用的是旧的Node库,它触发'data'事件,并拥有仅做查询用的pause()方法,那么你能使用wrap()方法来创建一个Readable流来使用旧版本的流,作为数据源。

你应该很少需要用到这个函数,但它会留下方便和旧版本的Node程序和库交互。

例如:

javascriptvar OldReader = require('./old-api-module.js').OldReader;var oreader = new OldReader;var Readable = require('stream').Readable;var myReader = new Readable().wrap(oreader);myReader.on('readable', function() {  myReader.read(); // etc.});

类: stream.Writable

可写流(Writable stream )接口是你正把数据写到一个目标的抽象。

可写流(Writable stream )的例子包括:

writable.write(chunk[, encoding][, callback])

  • chunk {String | Buffer} 准备写的数据
  • encoding {String} 编码方式(如果chunk 是字符串)
  • callback {Function} 数据块写入后的回调
  • 返回: {Boolean} 如果数据已被全部处理返回true

这个方法向底层系统写入数据,并在数据处理完毕后调用所给的回调。

返回值表示你是否应该继续立即写入。如果数据要缓存在内部,将会返回false。否则返回true

返回值仅供参考。即使返回false,你也可能继续写。但是写会缓存在内存里,所以不要做的太过分。最好的办法是等待drain事件后,再写入数据。

事件: 'drain'

如果调用writable.write(chunk)返回 false,drain事件会告诉你什么时候将更多的数据写入到流中。

javascript// Write the data to the supplied 可写流(Writable stream ) 1MM times.// Be attentive to back-pressure.function writeOneMillionTimes(writer, data, encoding, callback) {  var i = 1000000;  write();  function write() {    var ok = true;    do {      i -= 1;      if (i === 0) {        // last time!        writer.write(data, encoding, callback);      } else {        // see if we should continue, or wait        // don't pass the callback, because we're not done yet.        ok = writer.write(data, encoding);      }    } while (i > 0 && ok);    if (i > 0) {      // had to stop early!      // write some more once it drains      writer.once('drain', write);    }  }}

writable.cork()

强制缓存所有写入。

调用.uncork().end()后,会把缓存数据写入。

writable.uncork()

写入所有.cork()调用之后缓存的数据。

writable.setDefaultEncoding(encoding)

  • encoding {String} 新的默认编码
  • 返回:Boolean

给写数据流设置默认编码方式,如编码有效,则返回true ,否则返回false

writable.end([chunk][, encoding][, callback])

  • chunk {String | Buffer} 可选,要写入的数据
  • encoding {String} 编码方式(如果chunk是字符串)
  • callback {Function} 可选, stream结束时的回调函数

当没有更多的数据写入的时候调用这个方法。如果给出,回调会被用作finish事件的监听器。

调用end()后调用write()会产生错误。

javascript// write 'hello, ' and then end with 'world!'var file = fs.createWriteStream('example.txt');file.write('hello, ');file.end('world!');// writing more now is not allowed!

事件: 'finish'

调用end()方法后,并且所有的数据已经写入到底层系统,将会触发这个事件。

javascriptvar writer = getWritableStreamSomehow();for (var i = 0; i < 100; i ++) {  writer.write('hello, #' + i + '!
');}writer.end('this is the end
');writer.on('finish', function() {  console.error('all writes are now complete.');});

事件: 'pipe'

  • src {Readable Stream} 是导流(pipe)到可写流的源流。

无论何时在可写流(Writable stream )上调用pipe()方法,都会触发'pipe'事件,添加这个流到目标。

javascriptvar writer = getWritableStreamSomehow();var reader = getReadableStreamSomehow();writer.on('pipe', function(src) {  console.error('something is piping into the writer');  assert.equal(src, reader);});reader.pipe(writer);

事件: 'unpipe'

  • src {Readable Stream}未写入此可写的源流。

无论何时在可写流(Writable stream )上调用unpipe()方法,都会触发'unpipe'事件,将这个流从目标上移除。

javascriptvar writer = getWritableStreamSomehow();var reader = getReadableStreamSomehow();writer.on('unpipe', function(src) {  console.error('something has stopped piping into the writer');  assert.equal(src, reader);});reader.pipe(writer);reader.unpipe(writer);

事件: 'error'

  • {Error object}

写或导流(pipe)数据时,如果有错误会触发。

类: stream.Duplex

双工流(Duplex streams)是同时实现了ReadableWritable 接口。用法详见下文。

双工流(Duplex streams) 的例子包括:

类: stream.Transform

转换流(Transform streams)是双工Duplex流,它的输出是从输入计算得来。 它实现了ReadableWritable接口. 用法详见下文.

转换流(Transform streams)的例子包括:

流实现程序API

无论实现什么形式的流,模式都是一样的:

  1. 在你的子类中扩展适合的父类。 (util.inherits方法很有帮助)
  2. 在你的构造函数中调用父类的构造函数,以确保内部的机制初始化正确。
  3. 实现一个或多个方法,如下所列。

所扩展的类和要实现的方法取决于你要编写的流类。

Use-case

Class

方法(s) to implement

Reading only

[Readable](#stream_class_stream_readable_1)

[_read][]

Writing only

[Writable](#stream_class_stream_writable_1)

[_write][]

Reading and writing

[Duplex](#stream_class_stream_duplex_1)

[_read][], [_write][]

Operate on written data, then read the result

[Transform](#stream_class_stream_transform_1)

_transform, _flush

在你的代码里,千万不要调用流实现程序API里的方法。否则可能会引起消费流的程序副作用。

类: stream.Readable

stream.Readable是一个可被扩充的、实现了底层_read(size)方法的抽象类。

参照之前的流实现程序API查看如何在你的程序里消费流。以下内容解释了在你的程序里如何实现可读流(Readable stream)。

Example: 计数流

这是可读流(Readable stream)的基础例子,它将从1至1,000,000递增地触发数字,然后结束:

javascriptvar Readable = require('stream').Readable;var util = require('util');util.inherits(Counter, Readable);function Counter(opt) {  Readable.call(this, opt);  this._max = 1000000;  this._index = 1;}Counter.prototype._read = function() {  var i = this._index++;  if (i > this._max)    this.push(null);  else {    var str = '' + i;    var buf = new Buffer(str, 'ascii');    this.push(buf);  }};

Example: 简单协议 v1 (初始版)

和之前描述的parseHeader函数类似,但它被实现为自定义流。注意这个实现不会将输入数据转换为字符串。

实际上,更好的办法是将他实现为Transform流,使用下面的实现方法会更好:

javascript// A parser for a simple data protocol.// "header" is a JSON object, followed by 2 
 characters, and// then a message body.//// 注意: This can be done more simply as a Transform stream!// Using Readable directly for this is sub-optimal.  See the// alternative example below under Transform section.var Readable = require('stream').Readable;var util = require('util');util.inherits(SimpleProtocol, Readable);  function SimpleProtocol(source, options) {  if (!(this instanceof SimpleProtocol))    return new SimpleProtocol(source, options);  Readable.call(this, options;  this._inBody = false;  this._sawFirstCr = false;  // source is 可读流(Readable stream), such as a socket or file  this._source = source;  var self = this;  source.on('end', function() {    self.push(null);  });  // give it a kick whenever the source is readable  // read(0) will not consume any bytes  source.on('readable', function() {    self.read(0);  });  this._rawHeader = [];  this.header = null;}SimpleProtocol.prototype._read = function(n) {  if (!this._inBody) {    var chunk = this._source.read();    // if the source doesn't have data, we don't have data yet.    if (chunk === null)      return this.push('');    // check if the chunk has a 

    var split = -1;    for (var i = 0; i < chunk.length; i++) {      if (chunk[i] === 10) { // '
'        if (this._sawFirstCr) {          split = i;          break;        } else {          this._sawFirstCr = true;        }      } else {        this._sawFirstCr = false;      }    }    if (split === -1) {      // still waiting for the 

      // stash the chunk, and try again.      this._rawHeader.push(chunk);      this.push('');    } else {      this._inBody = true;      var h = chunk.slice(0, split);      this._rawHeader.push(h);      var header = Buffer.concat(this._rawHeader).toString();      try {        this.header = JSON.parse(header);      } catch (er) {        this.emit('error', new Error('invalid simple protocol data'));        return;      }      // now, because we got some extra data, unshift the rest      // back into the 读取队列 so that our consumer will see it.      var b = chunk.slice(split);      this.unshift(b);      // and let them know that we are done parsing the header.      this.emit('header', this.header);    }  } else {    // from there on, just provide the data to our consumer.    // careful not to push(null), since that would indicate EOF.    var chunk = this._source.read();    if (chunk) this.push(chunk);  }};// Usage:// var parser = new SimpleProtocol(source);// Now parser is 可读流(Readable stream) that will emit 'header'// with the parsed header data.

new stream.Readable([options])

  • options{Object}
    • highWaterMark{Number} 停止从底层资源读取数据前,存储在内部缓存的最大字节数;默认=16kb, objectMode流是16.
    • encoding{String} 若指定,则Buffer会被解码成所给编码的字符串,默认为null。
    • objectMode{Boolean} 该流是否为对象的流。意思是说stream.read(n)返回一个单独的值,而不是大小为n的Buffer。

Readable的扩展类中,确保调用了Readable的构造函数,这样才能正确初始化。

readable._read(size)

  • size{Number} 异步读取的字节数

注意:实现这个函数,但不要直接调用。

这个函数不要直接调用。在子类里实现,仅能被内部的Readable类调用。

所有可读流(Readable stream) 的实现必须停供一个_read方法,从底层资源里获取数据。

这个方法以下划线开头,是因为对于定义它的类是内部的,不会被用户程序直接调用。你可以在自己的扩展类中实现。

当数据可用时,通过调用readable.push(chunk)将之放到读取队列中。再次调用_read,需要继续推出更多数据。

size参数仅供参考。调用“read”可以知道知道应当抓取多少数据;其余与之无关的实现,比如TCP或TLS,则可忽略这个参数,并在可用时返回数据。例如,没有必要“等到”size个字节可用时才调用stream.push(chunk)。

readable.push(chunk[, encoding])

  • chunk {Buffer | null | String} 推入到读取队列的数据块
  • encoding {String} 字符串块的编码。必须是有效的Buffer编码,比如utf8或ascii。
  • 返回{Boolean}是否应该继续推入

注意: 这个函数必须被 Readable 实现者调用, 而不是可读流(Readable stream)的消费者.

_read()函数直到调用push(chunk)后才能被再次调用。

Readable类将数据放到读取队列,当'readable'事件触发后,被read()方法取出。push()方法会插入数据到读取队列中。如果调用了null,会触发数据结束信号 (EOF)。

这个API被设计成尽可能地灵活。比如说,你可以包装一个低级别的,具备某种暂停/恢复机制,和数据回调的数据源。这种情况下,你可以通过这种方式包装低级别来源对象:

javascript// source is an object with readStop() and readStart() 方法s,// and an `ondata` member that gets called when it has data, and// an `onend` member that gets called when the data is over.util.inherits(SourceWrapper, Readable);function SourceWrapper(options) {  Readable.call(this, options);  this._source = getLowlevelSourceObject();  var self = this;  // Every time there's data, we push it into the internal buffer.  this._source.ondata = function(chunk) {    // if push() 返回 false, then we need to stop reading from source    if (!self.push(chunk))      self._source.readStop();  };  // When the source ends, we push the EOF-signaling `null` chunk  this._source.onend = function() {    self.push(null);  };}// _read will be called when the stream wants to pull more data in// the advisory size 参数 is ignored in this case.SourceWrapper.prototype._read = function(size) {  this._source.readStart();};

类: stream.Writable

stream.Writable是个抽象类,它扩展了一个底层的实现_write(chunk, encoding, callback)方法.

参考上面的流实现程序API,来了解在你的程序里如何消费可写流。下面内容介绍了如何在你的程序里实现可写流。

new stream.Writable([options])

  • options {Object}
    • highWaterMark {Number} 当write()返回false时的缓存级别。默认=16kb,objectMode流是16。
    • decodeStrings {Boolean} 传给_write()前是否解码为字符串。默认=true
    • objectMode {Boolean}write(anyObj)是否是有效操作;如果为true,可以写任意数据,而不仅仅是Buffer/String。默认=false

请确保Writable类的扩展类中,调用构造函数以便缓冲设定能被正确初始化。

writable._write(chunk, encoding, callback)

  • chunk {Buffer | String} 要写入的数据块。总是buffer, 除非decodeStrings选项为false
  • encoding {String} 如果数据块是字符串,这个参数就是编码方式。如果是缓存,则忽略。注意,除非decodeStrings被设置为false,否则这个数据块一直是buffer。
  • callback{函数} 当你处理完数据后调用这个函数 (错误参数为可选参数)。

所以可写流(Writable stream ) 实现必须提供一个_write()方法,来发送数据给底层资源。

注意: 这个函数不能直接调用,由子类实现,仅内部可写方法可以调用。

使用标准的callback(error)方法调用回调函数,来表明写入完成或遇到错误。

如果构造函数选项中设定了decodeStrings标识,则chunk可能会是字符串而不是Buffer,encoding表明了字符串的格式。这种设计是为了支持对某些字符串数据编码提供优化处理的实现。如果你没有明确的设置decodeStringsfalse,这样你就可以安不管encoding参数,并假定chunk一直是一个缓存。

该方法以下划线开头,是因为对于定义它的类来说,这个方法是内部的,并且不应该被用户程序直接调用。你应当在你的扩充类中重写这个方法。

writable._writev(chunks, callback)

  • chunks {Array} 准备写入的数据块,每个块格式如下:{ chunk: ..., encoding: ... }.
  • callback {函数} 当你处理完数据后调用这个函数 (错误参数为可选参数)。

注意: 这个函数不能直接调用。由子类实现,仅内部可写方法可以调用.

这个函数的实现是可选的。多数情况下,没有必要实现。如果实现,将会在所有数据块缓存到写队列后调用。

类: stream.Duplex

双工流(duplex stream)同时兼具可读和可写特性,比如一个TCP socket连接。

注意stream.Duplex可以像Readable或Writable一样被扩充,实现了底层_read(sise) 和_write(chunk, encoding, callback) 方法的抽象类。

由于JavaScript并没有多重继承能力,因此这个类继承自Readable,寄生自Writable.从而让用户在双工扩展类中同时实现低级别的_read(n)方法和低级别的_write(chunk, encoding, callback)方法。

new stream.Duplex(options)

  • options {Object} 传递Writable and Readable构造函数,有以下的内容:
    • allowHalfOpen {Boolean} 默认=true。 如果设置为false,当写端结束的时候,流会自动的结束读端,反之亦然。
    • readableObjectMode {Boolean} 默认=false。将objectMode设为读端的流,如果为true,将没有效果。
    • writableObjectMode {Boolean} 默认=false。将objectMode设为写端的流,如果为true,将没有效果。

扩展自Duplex的类,确保调用了父亲的构造函数,保证缓存设置能正确初始化。

类: stream.Transform

转换流(transform class) 是双工流(duplex stream),输入输出端有因果关系,比如,zlib流或crypto流。

输入输出没有要求大小相同,块数量相同,到达时间相同。例如,一个Hash流只会在输入结束时产生一个数据块的输出;一个zlib流会产生比输入小得多或大得多的输出。

转换流(transform class) 必须实现_transform()方法,而不是_read()_write()方法,也可以实现_flush()方法(参见如下)。

new stream.Transform([options])

  • options {Object} 传递给Writable和Readable构造函数。

扩展自转换流(transform class) 的类,确保调用了父亲的构造函数,保证缓存设置能正确初始化。

transform._transform(chunk, encoding, callback)

  • chunk {Buffer | String} 准备转换的数据块。是buffer,除非decodeStrings选项设置为false
  • encoding {String} 如果数据块是字符串, 这个参数就是编码方式,否则就忽略这个参数
  • callback {函数} 当你处理完数据后调用这个函数 (错误参数为可选参数)。

注意:这个函数不能直接调用。由子类实现,仅内部可写方法可以调用.

所有的转换流(transform class) 实现必须提供 _transform方法来接收输入,并生产输出。

_transform可以做转换流(transform class)里的任何事,处理写入的字节,传给接口的写端,异步I/O,处理事情等等。

调用transform.push(outputChunk)0次或多次,从这个输入块里产生输出,依赖于你想要多少数据作为输出。

仅在当前数据块完全消费后调用这个回调。

注意,输入块可能有,也可能没有对应的输出块。如果你提供了第二个参数,将会传给push方法。如下述的例子:

javascripttransform.prototype._transform = function (data, encoding, callback) {  this.push(data);  callback();}transform.prototype._transform = function (data, encoding, callback) {  callback(null, data);}

该方法以下划线开头,是因为对于定义它的类来说,这个方法是内部的,并且不应该被用户程序直接调用。你应当在你的扩充类中重写这个方法。

transform._flush(callback)

  • callback {函数} 当你处理完数据后调用这个函数 (错误参数为可选参数)

注意:这个函数不能直接调用。由子类实现,仅内部可写方法可以调用.

某些情况下,转换操作可能需要分发一点流最后的数据。例如,Zlib流会存储一些内部状态,以便优化压缩输出。

有些时候,你可以实现_flush方法,它可以在最后面调用,当所有的写入数据被消费后,分发end告诉读端。和_transform一样,当刷新操作完毕, transform.push(chunk)为0次或更多次数。

该方法以下划线开头,是因为对于定义它的类来说,这个方法是内部的,并且不应该被用户程序直接调用。你应当在你的扩充类中重写这个方法。

事件: 'finish' and 'end'

finishend事件 分别来自Writable和Readable类。.end()事件结束后调用finish事件,所有的数据已经被_transform处理完毕,调用_flush后,所有的数据输出完毕,触发end

Example:SimpleProtocolparser v2

上面的简单协议分析例子列子可以通过使用高级别的Transform流来实现,和parseHeaderSimpleProtocol v1列子类似。

在这个示例中,输入会被导流到解析器中,而不是作为参数提供。这种做法更符合Node流的惯例。

javascriptvar util = require('util');var Transform = require('stream').Transform;util.inherits(SimpleProtocol, Transform);function SimpleProtocol(options) {  if (!(this instanceof SimpleProtocol))    return new SimpleProtocol(options);  Transform.call(this, options);  this._inBody = false;  this._sawFirstCr = false;  this._rawHeader = [];  this.header = null;}SimpleProtocol.prototype._transform = function(chunk, encoding, done) {  if (!this._inBody) {    // check if the chunk has a 

    var split = -1;    for (var i = 0; i < chunk.length; i++) {      if (chunk[i] === 10) { // '
'        if (this._sawFirstCr) {          split = i;          break;        } else {          this._sawFirstCr = true;        }      } else {        this._sawFirstCr = false;      }    }    if (split === -1) {      // still waiting for the 

      // stash the chunk, and try again.      this._rawHeader.push(chunk);    } else {      this._inBody = true;      var h = chunk.slice(0, split);      this._rawHeader.push(h);      var header = Buffer.concat(this._rawHeader).toString();      try {        this.header = JSON.parse(header);      } catch (er) {        this.emit('error', new Error('invalid simple protocol data'));        return;      }      // and let them know that we are done parsing the header.      this.emit('header', this.header);      // now, because we got some extra data, emit this first.      this.push(chunk.slice(split));    }  } else {    // from there on, just provide the data to our consumer as-is.    this.push(chunk);  }  done();};// Usage:// var parser = new SimpleProtocol();// source.pipe(parser)// Now parser is 可读流(Readable stream) that will emit 'header'// with the parsed header data.

类: stream.PassThrough

这是Transform流的简单实现,将输入的字节简单的传递给输出。它的主要用途是测试和演示。偶尔要构建某种特殊流时也会用到。

流: 内部细节

缓冲

可写流(Writable streams )和可读流(Readable stream)都会缓存数据到内部对象上,叫做_writableState.buffer_readableState.buffer

缓存的数据量,取决于构造函数是传入的highWaterMark参数。

调用stream.push(chunk)时,缓存数据到可读流(Readable stream)。在数据消费者调用stream.read()前,数据会一直缓存在内部队列中。

调用stream.write(chunk)时,缓存数据到可写流(Writable stream)。即使write()返回false

流(尤其是pipe()方法)得目的是限制数据的缓存量到一个可接受的水平,使得不同速度的源和目的不会淹没可用内存。

stream.read(0)

某些时候,你可能想不消费数据的情况下,触发底层可读流(Readable stream)机制的刷新。这种情况下可以调用stream.read(0),它总会返回null。

如果内部读取缓冲低于highWaterMark,并且流当前不在读取状态,那么调用read(0)会触发一个低级_read调用。

虽然基本上没有必要这么做。但你在Node内部的某些地方看到它确实这么做了,尤其是在Readable流类的内部。

stream.push('')

推一个0字节的字符串或缓存 (不在Object mode时)会发送有趣的副作用。 因为它是一个对stream.push()的调用,它将会结束reading进程。然而,它没有添加任何数据到可读缓冲区中,所以没有东西可供用户消费。

少数情况下,你当时没有提供数据,但你的流的消费者(或你的代码的其它部分)会通过调用stream.read(0)得知何时再次检查。在这种情况下,你可以调用 stream.push('')

到目前为止,这个功能唯一一个使用情景是在tls.CryptoStream类中,但它将在Node v0.12中被废弃。如果你发现你不得不使用stream.push(''),请考虑另一种方式。

和老版本的兼容性

v0.10版本前,可读流(Readable stream)接口比较简单,因此功能和用处也小。

  • 'data'事件会立即开始触发,而不会等待你调用read()方法。如果你需要进行某些I/O来决定如何处理数据,那么你只能将数据块储存到某种缓冲区中以防它们流失。
  • pause()方法仅供参考,而不保证生效。这意味着,即便流处于暂停状态时,你仍然需要准备接收'data'事件。

在Node v0.10中, 加入了下文所述的Readable类。为了考虑向后兼容,添加了'data'事件监听器或resume()方法被调用时,可读流(Readable stream)会切换到 "流动模式(flowing mode)"。其作用是,即便你不使用新的read()方法和'readable'事件,你也不必担心丢失'data'数据块。

大多数程序会维持正常功能。然而,下列条件下也会引入边界情况:

  • 没有添加 ['data'事件][]处理器
  • 从来没有调用resume()方法
  • 流从来没有被倒流(pipe)到任何可写目标上、

例如:

javascript// WARNING!  BROKEN!net.createServer(function(socket) {  // we add an 'end' 方法, but never consume the data  socket.on('end', function() {    // It will never get here.    socket.end('I got your message (but didnt read it)
');  });}).listen(1337);

v0.10版本前的Node,流入的消息数据会被简单的抛弃。之后的版本,socket会一直保持暂停。

这种情形下,调用resume()方法来开始工作:

javascript// Workaroundnet.createServer(function(socket) {  socket.on('end', function() {    socket.end('I got your message (but didnt read it)
');  });  // start the flow of data, discarding it.  socket.resume();}).listen(1337);

可读流(Readable stream)切换到流动模式(flowing mode),v0.10 版本前,可以使用wrap()方法将风格流包含在一个可读类里。

Object Mode

通常情况下,流仅操作字符串和缓存。

处于object mode的流,除了缓存和字符串,还可以可以读出普通JavaScript值。

在对象模式里,可读流(Readable stream) 调用stream.read(size)总会返回单个项目,无论是什么参数。

在对象模式里, 可写流(Writable stream ) 总会忽略传给stream.write(data, encoding)encoding参数。

特殊值null在对象模式里,依旧保持它的特殊性。也就说,对于对象模式的可读流(Readable stream),stream.read()返回null意味着没有更多数据,同时stream.push(null)会告知流数据结束(EOF)。

Node核心不存在对象模式的流,这种设计只被某些用户态流式库所使用。

应该在你的子类构造函数里,设置objectMode。在过程中设置不安全。

对于双工流(Duplex streams),objectMode可以用readableObjectModewritableObjectMode分别为读写端分别设置。这些选项,被转换流(Transform streams)用来实现解析和序列化。

javascriptvar util = require('util');var StringDecoder = require('string_decoder').StringDecoder;var Transform = require('stream').Transform;util.inherits(JSONParseStream, Transform);// Gets 
-delimited JSON  string data, and emits the parsed objectsfunction JSONParseStream() {  if (!(this instanceof JSONParseStream))    return new JSONParseStream();  Transform.call(this, { readableObjectMode : true });  this._buffer = '';  this._decoder = new StringDecoder('utf8');}JSONParseStream.prototype._transform = function(chunk, encoding, cb) {  this._buffer += this._decoder.write(chunk);  // split on newlines  var lines = this._buffer.split(/
?
/);  // keep the last partial line buffered  this._buffer = lines.pop();  for (var l = 0; l < lines.length; l++) {    var line = lines[l];    try {      var obj = JSON.parse(line);    } catch (er) {      this.emit('error', er);      return;    }    // push the parsed object out to the readable consumer    this.push(obj);  }  cb();};JSONParseStream.prototype._flush = function(cb) {  // Just handle any leftover  var rem = this._buffer.trim();  if (rem) {    try {      var obj = JSON.parse(rem);    } catch (er) {      this.emit('error', er);      return;    }    // push the parsed object out to the readable consumer    this.push(obj);  }  cb();};


稳定性: 3 - 稳定

net模块提供了异步网络封装,该Node.js模块包含了创建服务器/客户端的方法(调用 streams),你可以通过调用 require('net') 包含这个模块,访问方法如下所示:

const net = require('net');

net.createServer([options][, connectionListener])

创建一个TCP服务器。参数connectionListener自动给'connection'事件创建监听器。

options包含有以下默认值:

{  allowHalfOpen: false,  pauseOnConnect: false}

如果allowHalfOpen=true,当另一端socket发送FIN包时,socket不会自动发送FIN包。socket变为不可读,但仍可写。你需要显式的调用end()方法。更多信息参见'end'事件。

如果pauseOnConnect=true,当连接到来的时候相关联的socket将会暂停。它允许在初始进程不读取数据情况下,让连接在进程间传递。调用resume()从暂停的socket里读取数据。

下面是一个监听8124端口连接的应答服务器的例子:

var net = require('net');var server = net.createServer(function(c) { //'connection' listener  console.log('client connected');  c.on('end', function() {    console.log('client disconnected');  });  c.write('hello
');  c.pipe(c);});server.listen(8124, function() { //'listening' listener  console.log('server bound');});

使用telnet来测试:

telnet localhost 8124

要监听socket t/tmp/echo.sock,仅需要改倒数第三行代码,如下所示:

server.listen('/tmp/echo.sock', function() { //'listening' listener

使用nc连接到一个UNIX domain socket服务器:

nc -U /tmp/echo.sock

net.connect(options[, connectionListener])

net.createConnection(options[, connectionListener])

工厂方法,返回一个新的'net.Socket',并连接到指定的地址和端口。

当socket建立的时候,将会触发'connect'事件。

'net.Socket'有相同的方法。

对于TCP sockets,参数options因为下列参数的对象:

  • port: 客户端连接到Port的端口(必须)。

  • host: 客户端要连接到得主机。默认'localhost'.

  • localAddress: 网络连接绑定的本地接口。

  • localPort: 网络连接绑定的本地端口。

  • family : IP栈版本。默认4

对于本地域socket,参数options因为下列参数的对象:

  • path: 客户端连接到得路径(必须).

通用选项:

  • 如果allowHalfOpen=true, 当另一端socket发送FIN包时socket不会自动发送FIN包。socket变为不可读,但仍可写。你需要显式的调用end()方法。更多信息参见'end'事件。

    connectListener参数将会作为监听器添加到'connect'事件上。

下面是一个用上述方法应答服务器的客户端例子:

var net = require('net');var client = net.connect({port: 8124},    function() { //'connect' listener  console.log('connected to server!');  client.write('world!
');});client.on('data', function(data) {  console.log(data.toString());  client.end();});client.on('end', function() {  console.log('disconnected from server');});

要连接到socket/tmp/echo.sock,仅需将第二行代码改为如下的内容:

var client = net.connect({path: '/tmp/echo.sock'});

net.connect(port[, host][, connectListener])

net.createConnection(port[, host][, connectListener])

创建一个到端口port和主机host的TCP连接。如果忽略主机host,则假定为'localhost'。参数connectListener将会作为监听器添加到'connect'事件。

这是工厂方法,返回一个新的'net.Socket'

net.connect(path[, connectListener])

net.createConnection(path[, connectListener])

创建到path的unix socket连接。参数connectListener将会作为监听器添加到'connect'事件上。

这是工厂方法,返回一个新的'net.Socket'

Class: net.Server

这个类用来创建一个TCP或本地服务器。

server.listen(port[, host][, backlog][, callback])

开始接受指定端口port和主机host的连接。如果忽略主机host, 服务器将会接受任何IPv4地址(INADDR_ANY)的直接连接。端口为0,则会分配一个随机端口。

积压量(Backlog)为连接等待队列的最大长度。实际长度由您的操作系统通过 sysctl 设定,比如 linux 上的tcp_max_syn_backlogsomaxconn。这个参数默认值是511(不是512)。

这是异步函数。当服务器被绑定时会触发'listening'事件。最后一个参数callback将会作为'listening'事件的监听器。

有些用户会遇到EADDRINUSE错误,它表示另外一个服务器已经运行在所请求的端口上。处理这个情况的办法是等一段事件再重试:

server.on('error', function (e) {  if (e.code == 'EADDRINUSE') {    console.log('Address in use, retrying...');    setTimeout(function () {      server.close();      server.listen(PORT, HOST);    }, 1000);  }});

(注意:Node中的所有socket已设置了SO_REUSEADDR)

server.listen(path[, callback])

  • path {String}
  • callback {Function}

启动一个本地socket服务器,监听指定path的连接。

这是异步函数。绑定服务器后,会触发'listening'事件。最后一个参数callback将会作为'listening'事件的监听器。

UNIX上,本地域通常默认为UNIX域。参数path是文件系统路径,就和创建文件时一样,它也遵从命名规则和权限检查,并且在文件系统里可见,并持续到关闭关联。

Windows上,本地域通过命名管道实现。路径必须是以?pipe.pipe入口。任意字符串都可以,不过之后进行相同的管道命名处理,比如解决..序列。管道命名空间是平的。管道不会一直持久,当最后一个引用关闭的时候,管道将会移除。不要忘记javascript字符字符串转义要求路径使用双反斜杠,比如:

net.createServer().listen(    path.join('\?pipe', process.cwd(), 'myctl'))

server.listen(handle[, callback])

  • handle {Object}
  • callback {Function}

    handle 对象可以设置成server或socket(任意以下划线_handle开头的类),或者是{fd: <n>}对象。

这将是服务器用指定的句柄接收连接,前提是文件描述符或句柄已经绑定到端口或域socket。

Windows不支持监听文件句柄。

这是异步函数。当服务器已经被绑定,将会触发'listening'事件。最后一个参数callback将会作为'listening'事件的监听器。

server.listen(options[, callback])

  • options {Object} - 必须有。支持以下属性:
    • port {Number} - 可选。
    • host {String} - 可选。
    • backlog {Number} - 可选。
    • path {String} - 可选。
    • exclusive {Boolean} - 可选。
  • callback {Function} - 可选。

options的属性:端口port,主机host,和backlog,以及可选参数callback函数,他们在一起调用server.listen(port, [host], [backlog], [callback])。还有,参数path可以用来指定UNIX socket。

如果参数exclusivefalse(默认值),集群进程将会使用同一个句柄,允许连接共享。当参数exclusivetrue时,句柄不会共享,如果共享端口会返回错误。监听独家端口例子如下:

server.listen({  host: 'localhost',  port: 80,  exclusive: true});

server.close([callback])

服务器停止接收新的连接,保持现有连接。这是异步函数,当所有连接结束的时候服务器会关闭,并会触发'close'事件。你可以传一个回调函数来监听'close' 事件。如果存在,将会调用回调函数,错误(如果有)作为唯一参数。

server.address()

操作系统返回绑定的地址,协议族名和服务器端口。查找哪个端口已经被系统绑定时,非常有用。返回的对象有3个属性,比如:{ port: 12346, family: 'IPv4', address: '127.0.0.1' }

例如:

var server = net.createServer(function (socket) {  socket.end("goodbye
");});// grab a random port.server.listen(function() {  address = server.address();  console.log("opened server on %j", address);});

'listening'事件触发前,不要调用server.address()

server.unref()

如果这是事件系统中唯一一个活动的服务器,调用unref将允许程序退出。如果服务器已被unref,则再次调用unref并不会产生影响。

server.ref()

unref相反,如果这是唯一的服务器,在之前被unref了的服务器上调用ref将不会让程序退出(默认行为)。如果服务器已经被ref,则再次调用ref并不会产生影响。

server.maxConnections

设置这个选项后,当服务器连接数超过数量时拒绝新连接。

一旦已经用child_process.fork()方法将socket发送给子进程, 就不推荐使用这个选项。

server.connections

已经抛弃这个函数。请用server.getConnections()代替。服务器上当前连接的数量。

当调用child_process.fork()发送一个socket给子进程时,它将变为null。 要轮询子进程来获取当前活动连接的数量,请用server.getConnections代替。

server.getConnections(callback)

异步获取服务器当前活跃连接的数量。当socket发送给子进程后才有效;

回调函数有2个参数errcount

net.Server是事件分发器EventEmitter, 有以下事件:

事件: 'listening'

当服务器调用server.listen绑定后会触发。

事件:'connection'

  • {Socket object} 连接对象

当新连接创建后会被触发。socketnet.Socket实例。

事件: 'close'

服务器关闭时会触发。注意,如果存在连接,这个事件不会被触发直到所有的连接关闭。

事件: 'error'

  • {Error Object}

发生错误时触发。'close'事件将被下列事件直接调用。请查看server.listen例子。

Class: net.Socket

这个对象是TCP或UNIX Socket的抽象。net.Socket实例实现了一个双工流接口。他们可以在用户创建客户端(使用connect())时使用,或者由Node创建它们,并通过connection服务器事件传递给用户。

new net.Socket([options])

构造一个新的socket对象。

options对象有以下默认值:

{ fd: null  allowHalfOpen: false,  readable: false,  writable: false}

参数fd允许你指定一个存在的文件描述符。将readable和(或)writable设为true,允许在这个socket上读和(或)写(注意,仅在参数fd有效时)。关于allowHalfOpen,参见createServer()'end'事件。

socket.connect(port[, host][, connectListener])

socket.connect(path[, connectListener])

使用传入的socket打开一个连接。如果指定了端口port和主机host,TCP socket将打开socket。如果忽略参数host,则默认为localhost。如果指定了 path,socket将会被指定路径的unix socket打开。

通常情况不需要使用这个函数,比如使用net.createConnection打开socket。只有你实现了自己的socket时才会用到。

这是异步函数。当'connect'事件被触发时,socket已经建立。如果这是问题连接,'connect'事件不会被触发,将会抛出'error'事件。

参数connectListener将会作为监听器添加到'connect'事件。

socket.bufferSize

socket.bufferSize是net.Socket的一个属性,用于socket.write()。它能够帮助用户获取更快的运行速度。计算机不能一直处于写入大量数据状态--网络连接可能太慢。Node在内部会将排队数据写入到socket,并在网络可用时发送。(内部实现:轮询socket的文件描述符直到变为可写)。

这种内部缓冲的缺点是会增加内存使用量。这个属性表示当前准备写的缓冲字符数。(字符的数量等于准备写入的字节的数量,但是缓冲区可能包含字符串,这些字符串是惰性编码的,所以准确的字节数还无法知道)。

遇到很大增长很快的bufferSize时,用户可用尝试用pause()resume()来控制字符流。

socket.setEncoding([encoding])

设置socket的编码为可读流。更多信息参见stream.setEncoding()

socket.write(data[, encoding][, callback])

在socket上发送数据。第二个参数指定了字符串的编码,默认是UTF8编码。

如果所有数据成功刷新到内核缓冲区,返回true。如果数据全部或部分在用户内存里,返回false。当缓冲区为空的时候会触发'drain'

当数据最终被完整写入的的时候,可选的callback参数会被执行,但不一定会马上执行。

socket.end([data][, encoding])

半关闭socket。例如,它发送一个FIN包。可能服务器仍在发送数据。

如果参数data不为空,等同于调用socket.write(data, encoding)后再调用socket.end()

socket.destroy()

确保没有I/O活动在这个套接字上。只有在错误发生情况下才需要。(处理错误等等)。

socket.pause()

暂停读取数据。就是说,不会再触发data事件。对于控制上传非常有用。

socket.resume()

调用pause()后想恢复读取数据。

socket.setTimeout(timeout[, callback])

socket闲置时间超过timeout毫秒后 ,将socket设置为超时。

触发空闲超时事件时,socket将会收到'timeout'事件,但是连接不会被断开。用户必须手动调用end()destroy()这个socket。

如果timeout= 0,那么现有的闲置超时会被禁用

可选的callback参数将会被添加成为'timeout'事件的一次性监听器。

socket.setNoDelay([noDelay])

禁用纳格(Nagle)算法。默认情况下TCP连接使用纳格算法,在发送前他们会缓冲数据。将noDelay设置为true将会在调用socket.write()时立即发送数据。noDelay默认值为true

socket.setKeepAlive([enable][, initialDelay])

禁用/启用长连接功能,并在发送第一个在闲置socket上的长连接 probe 之前,可选地设定初始延时。默认为false。

设定initialDelay(毫秒),来设定收到的最后一个数据包和第一个长连接probe之间的延时。将initialDelay设为0,将会保留默认(或者之前)的值。默认值为0。

socket.address()

操作系统返回绑定的地址,协议族名和服务器端口。返回的对象有3个属性,比如{ port: 12346, family: 'IPv4', address: '127.0.0.1' }

socket.unref()

如果这是事件系统中唯一一个活动的服务器,调用unref将允许程序退出。如果服务器已被unref,则再次调用unref并不会产生影响。

socket.ref()

unref相反,如果这是唯一的服务器,在之前被unref了的服务器上调用ref将不会让程序退出(默认行为)。如果服务器已经被ref,则再次调用ref并不会产生影响。

socket.remoteAddress

远程的IP地址字符串,例如:'74.125.127.100'或者'2001:4860:a005::68'

socket.remoteFamily

远程IP协议族字符串,比如'IPv4'或者'IPv6'

socket.remotePort

远程端口,数字表示,例如:80或者21

socket.localAddress

网络连接绑定的本地接口远程客户端正在连接的本地IP地址,字符串表示。例如,如果你在监听'0.0.0.0'而客户端连接在'192.168.1.1',这个值就会是 '192.168.1.1'。

socket.localPort

本地端口地址,数字表示。例如:80或者21

socket.bytesRead

接收到得字节数。

socket.bytesWritten

发送的字节数。

net.Socket是事件分发器EventEmitter的实例, 有以下事件:

事件: 'lookup'

在解析域名后,但在连接前,触发这个事件。对UNIX sokcet不适用。

  • err {Error | Null} 错误对象。参见dns.lookup().
  • address {String} IP地址。
  • family {String | Null} 地址类型。参见dns.lookup().

事件: 'connect'

当成功建立socket连接时触发。参见connect()

事件: 'data'

  • {Buffer object}

当接收到数据时触发。参数data可以是BufferString。使用socket.setEncoding()设定数据编码。(更多信息参见Readable Stream)。

Socket触发一个'data'事件时,如果没有监听器,数据将会丢失。

事件: 'end'

当socket另一端发送FIN包时,触发该事件。

默认情况下(allowHalfOpen == false),一旦socket将队列里的数据写完毕,socket将会销毁它的文件描述符。如果allowHalfOpen == true,socket不会从它这边自动调用end(),使的用户可以随意写入数据,而让用户端自己调用end()。

事件: 'timeout'

当socket空闲超时时触发,仅是表明socket已经空闲。用户必须手动关闭连接。

参见:socket.setTimeout()

事件: 'drain'

当写缓存为空得时候触发。可用来控制上传。

参见:socket.write()的返回值。

事件: 'error'

  • {Error object}

错误发生时触发。以下事件将会直接触发'close'事件。

事件: 'close'

  • had_error {Boolean} 如果socket传输错误,为true

当socket完全关闭时触发。参数had_error是boolean,它表示是否因为传输错误导致socket关闭。

net.isIP(input)

测试是否输入的为IP地址。字符串无效时返回0。IPV4情况下返回4,IPV6情况下返回6.

net.isIPv4(input)

如果输入的地址为IPV4,返回true,否则返回false。

net.isIPv6(input)

如果输入的地址为IPV6,返回true,否则返回false。


稳定性: 3 - 稳定

V8提供了强大的调试工具,可以通过TCP protocol从外部访问。Node内置这个调试工具客户端。使用这个调试器的方法是,以debug参数启动Node.js,将会出现提示,指示调试器成功启动:

% node debug myscript.js< debugger listening on port 5858connecting... okbreak in /home/indutny/Code/git/indutny/myscript.js:1  1 x = 5;  2 setTimeout(function () {  3   debugger;debug>

Node的调试器不支持所有的命令,但是简单的步进和检查还是可以的。在代码里嵌入debugger;,可以设置断点。

例:myscript.js代码如下:

// myscript.jsx = 5;setTimeout(function () {  debugger;  console.log("world");}, 1000);console.log("hello");

如果启动debugger,它会断在第四行:

% node debug myscript.js< debugger listening on port 5858connecting... okbreak in /home/indutny/Code/git/indutny/myscript.js:1  1 x = 5;  2 setTimeout(function () {  3   debugger;debug> cont< hellobreak in /home/indutny/Code/git/indutny/myscript.js:3  1 x = 5;  2 setTimeout(function () {  3   debugger;  4   console.log("world");  5 }, 1000);debug> nextbreak in /home/indutny/Code/git/indutny/myscript.js:4  2 setTimeout(function () {  3   debugger;  4   console.log("world");  5 }, 1000);  6 console.log("hello");debug> replPress Ctrl + C to leave debug repl> x5> 2+24debug> next< worldbreak in /home/indutny/Code/git/indutny/myscript.js:5  3   debugger;  4   console.log("world");  5 }, 1000);  6 console.log("hello");  7debug> quit%

repl命令能执行远程代码;next能步进到下一行。此外可以输入help查看哪些命令可用。

监视器-Watchers

调试的时候可以查看表达式和变量。每个断点处,监视器都会显示上下文。

输入watch("my_expression")开始监视表达式,watchers显示活跃的监视器。输入unwatch("my_expression")可以移除监视器。

命令参考-Commands reference

步进-Stepping

  • cont, c- 继续执行
  • next, n- Step next
  • step, s- Step in
  • out, o- Step out
  • pause- 暂停 (类似开发工具的暂停按钮)

断点Breakpoints

  • setBreakpoint(), sb()- 当前行设置断点
  • setBreakpoint(line), sb(line)- 在指定行设置断点
  • setBreakpoint('fn()'), sb(...)- 在函数里的第一行设置断点
  • setBreakpoint('script.js', 1), sb(...)- 在 script.js 第一行设置断点。
  • clearBreakpoint, cb(...)- 清除断点

也可以在尚未加载的文件里设置断点:

% ./node debug test/fixtures/break-in-module/main.js< debugger listening on port 5858connecting to port 5858... okbreak in test/fixtures/break-in-module/main.js:1  1 var mod = require('./mod.js');  2 mod.hello();  3 mod.hello();debug> setBreakpoint('mod.js', 23)Warning: script 'mod.js' was not loaded yet.  1 var mod = require('./mod.js');  2 mod.hello();  3 mod.hello();debug> cbreak in test/fixtures/break-in-module/mod.js:23 21 22 exports.hello = function() { 23   return 'hello from module'; 24 }; 25debug>

信息Info

  • backtrace, bt- 打印当前执行框架的backtrace
  • list(5)- 显示脚本代码的5行上下文(之前5行和之后5行)
  • watch(expr)- 监视列表里添加表达式
  • unwatch(expr)- 从监视列表里删除表达式
  • watchers- 显示所有的监视器和它们的值(每个断点都会自动列出)
  • repl- 在所调试的脚本的上下文中,打开调试器的repl

执行控制Execution control

  • run- 运行脚本 (开始调试的时候自动运行)
  • restart- 重新运行脚本
  • kill- 杀死脚本

杂项Various

  • scripts- 列出所有已经加载的脚本
  • version- 显示v8版本

高级应用Advanced Usage

V8调试器可以用两种方法启用和访问,--debug命令启动调试,或向已经启动Node发送SIGUSR1

一旦一个进程进入调试模式,它可以被node调试器连接。调试器可以通过pid或URI来连接。

  • node debug -p <pid>- 通过pid连接进程
  • node debug <URI>- 通过URI(比如localhost:5858)连接进程w。


稳定性: 3 - 稳定

Node.js字符串解码器(string_decoder)模块的使用是通过require('string_decoder')实现的。

Node.js字符串解码器(string_decoder)用于将缓存(buffer)解码为字符串。这是buffer.toString()的简单接口,提供了utf8支持。

var StringDecoder = require('string_decoder').StringDecoder;var decoder = new StringDecoder('utf8');var cent = new Buffer([0xC2, 0xA2]);console.log(decoder.write(cent));var euro = new Buffer([0xE2, 0x82, 0xAC]);console.log(decoder.write(euro));

Class: StringDecoder

接受一个参数encoding,默认值为utf8

decoder.write(buffer)

返回解码后的字符串。

decoder.end()

返回buffer里剩下的末尾字节。


稳定性: 4 - API 冻结

Node.js系统(OS)模块提供一些与基本的操作系统有关的函数。

使用require('os')访问这个模块,如下所示:

const os = require('os');

os.tmpdir()

用于返回操作系统的默认临时文件夹。

os.endianness()

用于返回CPU的字节序,可能的是"BE"或"LE"。

os.hostname()

用于返回操作系统的主机名。

os.type()

用于返回操作系统名。

os.platform()

用于返回操作系统名

os.arch()

用于返回操作系统CPU架构,可能的值有"x64"、"arm"和"ia32"。

os.release()

用于返回操作系统的发行版本

os.uptime()

用于返回操作系统运行的时间,以秒为单位。

os.loadavg()

用于显示原文其他翻译纠错返回一个包含1、5、15分钟平均负载的数组。

平均负载是系统的一个指标,操作系统计算,用一个很小的数字表示。理论上来说,平均负载最好比系统里的CPU低。

平均负载是一个非常UNIX-y的概念,windows系统没有相同的概念。所以windows总是返回[0, 0, 0]

os.totalmem()

用于返回系统内存总量,单位为字节。

os.freemem()

用于返回操作系统空闲内存量,单位是字节。

os.cpus()

用于返回一个对象数组,包含所安装的每个CPU/内核的信息:型号、速度(单位 MHz)、时间(一个包含user、nice、sys、idle和irq所使用CPU/内核毫秒数的对象)。

os.cpus的例子:

[ { model: 'Intel(R) Core(TM) i7 CPU         860  @ 2.80GHz',    speed: 2926,    times:     { user: 252020,       nice: 0,       sys: 30340,       idle: 1070356870,       irq: 0 } },  { model: 'Intel(R) Core(TM) i7 CPU         860  @ 2.80GHz',    speed: 2926,    times:     { user: 306960,       nice: 0,       sys: 26980,       idle: 1071569080,       irq: 0 } },  { model: 'Intel(R) Core(TM) i7 CPU         860  @ 2.80GHz',    speed: 2926,    times:     { user: 248450,       nice: 0,       sys: 21750,       idle: 1070919370,       irq: 0 } },  { model: 'Intel(R) Core(TM) i7 CPU         860  @ 2.80GHz',    speed: 2926,    times:     { user: 256880,       nice: 0,       sys: 19430,       idle: 1070905480,       irq: 20 } },  { model: 'Intel(R) Core(TM) i7 CPU         860  @ 2.80GHz',    speed: 2926,    times:     { user: 511580,       nice: 20,       sys: 40900,       idle: 1070842510,       irq: 0 } },  { model: 'Intel(R) Core(TM) i7 CPU         860  @ 2.80GHz',    speed: 2926,    times:     { user: 291660,       nice: 0,       sys: 34360,       idle: 1070888000,       irq: 10 } },  { model: 'Intel(R) Core(TM) i7 CPU         860  @ 2.80GHz',    speed: 2926,    times:     { user: 308260,       nice: 0,       sys: 55410,       idle: 1071129970,       irq: 880 } },  { model: 'Intel(R) Core(TM) i7 CPU         860  @ 2.80GHz',    speed: 2926,    times:     { user: 266450,       nice: 1480,       sys: 34920,       idle: 1072572010,       irq: 30 } } ]

os.networkInterfaces()

获得网络接口列表的方法如下所示:

{ lo:   [ { address: '127.0.0.1',       netmask: '255.0.0.0',       family: 'IPv4',       mac: '00:00:00:00:00:00',       internal: true },     { address: '::1',       netmask: 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',       family: 'IPv6',       mac: '00:00:00:00:00:00',       internal: true } ],  eth0:   [ { address: '192.168.1.108',       netmask: '255.255.255.0',       family: 'IPv4',       mac: '01:02:03:0a:0b:0c',       internal: false },     { address: 'fe80::a00:27ff:fe4e:66a1',       netmask: 'ffff:ffff:ffff:ffff::',       family: 'IPv6',       mac: '01:02:03:0a:0b:0c',       internal: false } ] }

os.EOL

定义了操作系统的End-of-line的常量。


稳定性: 3 - 稳定

本节将介绍Node.js的DNS模块,你可以通过调用require('dns')来访问DNS模块。

DNS模块包含的函数属于2个不同的分类:

1)使用系统底层的特性,完成名字解析,这个过程不需要网络通讯,这个分类仅有一个函数:dns.lookup开发者在同一个系统里名字解析都是用 dns.lookup

下面的例子解析了www.google.com

var dns = require('dns');dns.lookup('www.google.com', function onLookup(err, addresses, family) {  console.log('addresses:', addresses);});

2)连接到DNS服务器进行名字解析,始终使用网络来进行域名查询。这个分类包含除了dns.lookup外的所有函数。这些函数不会和dns.lookup使用同一套配置文件。如果你不想使用系统底层的特性来进行名字解析,而想进行DNS查询的话,可以使用这个分类的函数。

下面的例子,解析了'www.google.com',并反向解析返回的IP地址:

var dns = require('dns');dns.resolve4('www.google.com', function (err, addresses) {  if (err) throw err;  console.log('addresses: ' + JSON.stringify(addresses));  addresses.forEach(function (a) {    dns.reverse(a, function (err, hostnames) {      if (err) {        throw err;      }      console.log('reverse for ' + a + ': ' + JSON.stringify(hostnames));    });  });});

更多细节参考Implementation considerations section

dns.lookup(hostname[, options], callback)

将域名(比如'google.com')解析为第一条找到的记录A (IPV4)或AAAA(IPV6)。参数options可以是一个对象或整数。如果没有提供options,IP v4和 v6地址都可以。如果options是整数,则必须是46

options参数可能是包含familyhints两个属性的对象。这两个属性都是可选的。如果提供了family,则必须是46,否则,IP v4和v6地址都可以。如果提供了hints,可以是一个或者多个getaddrinfo标志,若不提供,没有标志会传给getaddrinfo。多个标志位可以通过或运算来整合。以下的例子展示如何使用options

{  family: 4,  hints: dns.ADDRCONFIG | dns.V4MAPPED}

参见supported getaddrinfo flags 查看更多的标志位。

回调函数包含参数 (err, address, family)address参数表示IP v4或v6地址。family参数是4或6,表示address家族(不一定是之前传入lookup的值)。

出错时,参数errError对象,err.code是错误代码。请记住,err.code等于'ENOENT',不仅可能是因为域名不存在,还有可能是是其他原因,比如没有可用文件描述符。

dns.lookup不必和DNS协议有关系。它使用了操作系统的特性,能将名字和地址关联。

实现这些东西也许很简单,但是对于 Node.js 程序来说都重要,所以在使用前请花点时间阅读Implementation considerations section

dns.lookupService(address, port, callback)

使用getnameinfo解析传入的地址和端口为域名和服务。

这个回调函数的参数是(err, hostname, service)hostnameservice都是字符串 (比如'localhost''http')。

出错时,参数errError对象,err.code是错误代码。

dns.resolve(hostname[, rrtype], callback)

将一个域名(如'google.com')解析为一个rrtype指定记录类型的数组。

有效的rrtypes值为:

  • 'A' (IPV4地址,默认)
  • 'AAAA' (IPV6地址)
  • 'MX' (邮件交换记录)
  • 'TXT' (text记录)
  • 'SRV' (SRV记录)
  • 'PTR' (用来反向IP查找)
  • 'NS' (域名服务器记录)
  • 'CNAME' (别名记录)
  • 'SOA' (授权记录的初始值)

回调参数为(err, addresses). 其中addresses中每一项的类型都取决于记录类型,详见下文对应的查找方法。

出错时,参数errError 对象,err.code是错误代码。

dns.resolve4(hostname, callback)

dns.resolve()类似,仅能查询IPv4 (A记录)。addressesIPv4地址数组 (比如,['74.125.79.104', '74.125.79.105', '74.125.79.106'])。

dns.resolve6(hostname, callback)

dns.resolve4()类似,仅能查询 IPv4(AAAA查询)。

dns.resolveMx(hostname, callback)

dns.resolve()类似,仅能查询邮件交换(MX记录)。

addresses是MX记录数组,每一个包含优先级和交换属性(比如,[{'priority': 10, 'exchange': 'mx.example.com'},...])。

dns.resolveTxt(hostname, callback)

dns.resolve()类似,仅能进行文本查询 (TXT记录)。addresses是2-d文本记录数组。(比如,[ ['v=spf1 ip4:0.0.0.0 ', '~all' ] ])。每个子数组包含一条记录的TXT块。根据使用情况可以连接在一起,也可单独使用。

dns.resolveSrv(hostname, callback)

dns.resolve()类似,仅能进行服务记录查询 (SRV记录)。addresseshostname可用的SRV记录数组。SRV记录属性有优先级(priority),权重(weight), 端口(port), 和名字(name) (比如,[{'priority': 10, 'weight': 5, 'port': 21223, 'name': 'service.example.com'}, ...])。

dns.resolveSoa(hostname, callback)

dns.resolve()类似,仅能查询权威记录(SOA记录)。

addresses是包含以下结构的对象:

{  nsname: 'ns.example.com',  hostmaster: 'root.example.com',  serial: 2013101809,  refresh: 10000,  retry: 2400,  expire: 604800,  minttl: 3600}

dns.resolveNs(hostname, callback)

dns.resolve()类似,仅能进行域名服务器记录查询(NS记录)。addresses是域名服务器记录数组(hostname可以使用) (比如,['ns1.example.com', 'ns2.example.com'])。

dns.resolveCname(hostname, callback)

dns.resolve()类似,仅能进行别名记录查询 (CNAME记录)。addresses是对hostname可用的别名记录数组 (比如,['bar.example.com'])。

dns.reverse(ip, callback)

反向解析IP地址,返回指向该IP地址的域名数组。

回调函数参数(err, hostnames)

出错时,参数errError对象,err.code是错误代码。

dns.getServers()

返回一个用于当前解析的IP地址的数组的字符串。

dns.setServers(servers)

指定一组IP地址作为解析服务器。

如果你给地址指定了端口,端口会被忽略,因为底层库不支持。

传入无效参数,会抛出以下错误:

Error codes

每个DNS查询都可能返回以下错误:

  • dns.NODATA: DNS服务器返回无数据应答。
  • dns.FORMERR: DNS服务器声称查询格式错误。
  • dns.SERVFAIL: DNS服务器返回一般失败。
  • dns.NOTFOUND: 没有找到域名。
  • dns.NOTIMP: DNS服务器未实现请求的操作。
  • dns.REFUSED: DNS服务器拒绝查询。
  • dns.BADQUERY: DNS查询格式错误。
  • dns.BADNAME: 域名格式错误。
  • dns.BADFAMILY: 地址协议不支持。
  • dns.BADRESP: DNS回复格式错误。
  • dns.CONNREFUSED: 无法连接到DNS服务器。
  • dns.TIMEOUT: 连接DNS服务器超时。
  • dns.EOF: 文件末端。
  • dns.FILE: 读文件错误。
  • dns.NOMEM: 内存溢出。
  • dns.DESTRUCTION: 通道被摧毁。
  • dns.BADSTR: 字符串格式错误。
  • dns.BADFLAGS: 非法标识符。
  • dns.NONAME: 所给主机不是数字。
  • dns.BADHINTS: 非法HINTS标识符。
  • dns.NOTINITIALIZED: c c-ares库尚未初始化。
  • dns.LOADIPHLPAPI: 加载iphlpapi.dll出错。
  • dns.ADDRGETNETWORKPARAMS: 无法找到GetNetworkParams函数。
  • dns.CANCELLED: 取消DNS查询。

支持的 getaddrinfo 标志

以下内容可作为hints标志传给dns.lookup

  • dns.ADDRCONFIG: 返回当前系统支持的地址类型。例如,如果当前系统至少配置了一个IPv4地址,则返回IPv4地址。
  • dns.V4MAPPED: 如果指定了IPv6家族, 但是没有找到IPv6地址,将返回IPv4映射的IPv6地址。

Implementation considerations

虽然dns.lookupdns.resolve*/dns.reverse函数都能实现网络名和网络地址的关联,但是他们的行为不太一样。这些不同点虽然很巧妙,但是会对Node.js程序产生显著的影响。

dns.lookup

dns.lookup和绝大多数程序一样使用了相同的系统特性。例如,dns.lookupping命令用相同的方法解析了一个指定的名字。多数类似POSIX的系统,dns.lookup函数可以通过改变nsswitch.conf(5)和/或resolv.conf(5)的设置调整。如果改变这些文件将会影响系统里的其他应用。

虽然,JavaScript调用是异步的,它的实现是同步的调用libuv线程池里的getaddrinfo(3)。因为libuv线程池固定大小,所以如果调用getaddrinfo(3) 的时间太长,会使的池里的其他操作(比如文件操作)性能降低。为了降低这个风险,可以通过增加'UV_THREADPOOL_SIZE'的值,让它超过4,来调整libuv线程池大小,更多信息参见[the official libuvdocumentation](http://docs.libuv.org/en/latest/threadpool.html)。

dns.resolve, functions starting with dns.resolve and dns.reverse

这些函数的实现和dns.lookup不大相同。他们不会用到getaddrinfo(3),而是始终进行网络查询。这些操作都是异步的,和libuv线程池无关。

因此,这些操作对于其他线程不会产生负面影响,这和dns.lookup不同。

它们不会用到dns.lookup的配置文件(例如 ,/etc/hosts_)。


稳定性: 5 - 锁定

Node.js定时器模块提供了全局API,用于在以后的某个时间段调用函数。

所有的定时器函数都是全局的。不需要通过require()就可以访问。

setTimeout(callback, delay[, arg][, ...])

delay毫秒之后执行callback。返回timeoutObject对象,可能会用来clearTimeout()。你也可以给回调函数传参数。

需要注意,你的回调函数可能不会非常准确的在delay毫秒后执行,Node.js不保证回调函数的精确时间和执行顺序。回调函数会尽量的靠近指定的时间。

clearTimeout(timeoutObject)

阻止一个timeout被触发。

setInterval(callback, delay[, arg][, ...])

每隔delay毫秒就重复执行callback。返回timeoutObject对象,可能会用来clearTimeout()。你也可以给回调函数传参数。

clearInterval(intervalObject)

阻止一个interval被触发。

unref()

setTimeoutsetInterval所返回的值,拥有timer.unref()方法,它能让你创建一个活动的定时器,但是它所在的事件循环中如果仅剩它一个定时器,将不会保持程序运行。如果定时器已经调用了unref,再次调用将无效。

setTimeout场景中,当你使用unref并创建了一个独立定时器它将会唤醒事件循环。创建太多的这样的东西会影响事件循环性能,所以谨慎使用。

ref()

如果你之前已经使用unref()一个定时器,就可以使用ref()来明确的请求定时器保持程序打开状态。如果计时器已经调用了ref(),再次调用将无效。

setImmediate(callback[, arg][, ...])

setTimeoutsetInterval事件前,在输入/输出事件后,安排一个callback"immediate"立即执行。

immediates的回调以它们创建的顺序加入队列。整个回调队列会在事件循环迭代中执行。如果你将immediates加入到一个正在执行回调中,那么将不会触发immediate,直到下次事件循环迭代。

clearImmediate(immediateObject)

用于停止一个immediate的触发。


稳定性: 2 - 不稳定

Node.js域包含了能把不同的IO操作看成单独组的方法。如果任何一个注册到域的事件或者回调触发error事件,或者抛出一个异常,则域就会接收到通知,而不是在process.on('uncaughtException')处理程序中丢失错误的上下文,也不会使程序立即以错误代码退出。

警告:不要忽视错误!

你不能将域错误处理程序看做错误发生时就关闭进程的一个替代方案。

根据JavaScript中抛出异常的工作原理,基本上没有方法可以安全的“回到原先离开的位置”,在不泄露引用,或者不造成一些其他未定义的状态下。

响应抛出错误最安全的方法就是关闭进程。一个正常的服务器会可能有很多活跃的连接,因为某个错误就关闭所有连接显然是不合理的。

比较好的方法是给触发错误的请求发送错误响应,让其他连接正常工作时,停止监听触发错误的人的新请求。

按这种方法,和集群(cluster)模块可以协同工作,当某个进程遇到错误时,主进程可以复制一个新的进程。对于Node程序,终端代理或者注册的服务,可以留意错误并做出反应。

举例来说,下面的代码就不是好办法:

javascript// XXX WARNING!  BAD IDEA!var d = require('domain').create();d.on('error', function(er) {  // The error won't crash the process, but what it does is worse!  // Though we've prevented abrupt process restarting, we are leaking  // resources like crazy if this ever happens.  // This is no better than process.on('uncaughtException')!  console.log('error, but oh well', er.message);});d.run(function() {  require('http').createServer(function(req, res) {    handleRequest(req, res);  }).listen(PORT);});

通过使用域的上下文,并将程序切为多个工作进程,我们能够更合理的响应,处理错误更安全:

javascript// 好一些的做法!var cluster = require('cluster');var PORT = +process.env.PORT || 1337;if (cluster.isMaster) {  // In real life, you'd probably use more than just 2 workers,  // and perhaps not put the master and worker in the same file.  //  // You can also of course get a bit fancier about logging, and  // implement whatever custom logic you need to prevent DoS  // attacks and other bad behavior.  //  // See the options in the cluster documentation.  //  // The important thing is that the master does very little,  // increasing our resilience to unexpected errors.  cluster.fork();  cluster.fork();  cluster.on('disconnect', function(worker) {    console.error('disconnect!');    cluster.fork();  });} else {  // the worker  //  // This is where we put our bugs!  var domain = require('domain');  // See the cluster documentation for more details about using  // worker processes to serve requests.  How it works, caveats, etc.  var server = require('http').createServer(function(req, res) {    var d = domain.create();    d.on('error', function(er) {      console.error('error', er.stack);      // Note: we're in dangerous territory!      // By definition, something unexpected occurred,      // which we probably didn't want.      // Anything can happen now!  Be very careful!      try {        // make sure we close down within 30 seconds        var killtimer = setTimeout(function() {          process.exit(1);        }, 30000);        // But don't keep the process open just for that!        killtimer.unref();        // stop taking new requests.        server.close();        // Let the master know we're dead.  This will trigger a        // 'disconnect' in the cluster master, and then it will fork        // a new worker.        cluster.worker.disconnect();        // try to send an error to the request that triggered the problem        res.statusCode = 500;        res.setHeader('content-type', 'text/plain');        res.end('Oops, there was a problem!
');      } catch (er2) {        // oh well, not much we can do at this point.        console.error('Error sending 500!', er2.stack);      }    });    // Because req and res were created before this domain existed,    // we need to explicitly add them.    // See the explanation of implicit vs explicit binding below.    d.add(req);    d.add(res);    // Now run the handler function in the domain.    d.run(function() {      handleRequest(req, res);    });  });  server.listen(PORT);}// This part isn't important.  Just an example routing thing.// You'd put your fancy application logic here.function handleRequest(req, res) {  switch(req.url) {    case '/error':      // We do some async stuff, and then...      setTimeout(function() {        // Whoops!        flerb.bark();      });      break;    default:      res.end('ok');  }}

错误对象的附加内容

任何时候一个错误被路由传到一个域的时,会添加几个字段。

  • error.domain 第一个处理错误的域
  • error.domainEmitter 用这个错误对象触发'error'事件的事件分发器
  • error.domainBound 绑定到domain的回调函数,第一个参数是error。
  • error.domainThrown boolean值,表明是抛出错误,分发,或者传递给绑定的回到函数。

隐式绑定

新分发的对象(包括流对象(Stream objects),请求(requests),响应(responses)等)会隐式的绑定到当前正在使用的域中。

另外,传递给底层事件循环(比如fs.open或其他接收回调的方法)的回调函数将会自动的绑定到这个域。如果他们抛出异常,域会捕捉到错误信息。

为了避免过度使用内存,域对象不会象隐式的添加为有效域的子对象。如果这样做的话,很容易影响到请求和响应对象的垃圾回收。

如果你想将域对象作为子对象嵌入到父域里,就必须显式的添加它们。

隐式绑定路由抛出的错误和'error'事件,但是不会注册事件分发器到域,所以domain.dispose()不会关闭事件分发器。隐式绑定仅需注意抛出的错误和 'error'事件。

显式绑定

有时候正在使用的域并不是某个事件分发器的域。或者说,事件分发器可能在某个域里创建,但是被绑定到另外一个域里。

例如,HTTP服务器使用正一个域对象,但我们希望可以每一个请求使用一个不同的域。

这可以通过显式绑定来实现。

例如:

// create a top-level domain for the servervar serverDomain = domain.create();serverDomain.run(function() {  // server is created in the scope of serverDomain  http.createServer(function(req, res) {    // req and res are also created in the scope of serverDomain    // however, we'd prefer to have a separate domain for each request.    // create it first thing, and add req and res to it.    var reqd = domain.create();    reqd.add(req);    reqd.add(res);    reqd.on('error', function(er) {      console.error('Error', er, req.url);      try {        res.writeHead(500);        res.end('Error occurred, sorry.');      } catch (er) {        console.error('Error sending 500', er, req.url);      }    });  }).listen(1337);});

domain.create()

  • return: {Domain}

用于返回一个新的域对象。

Class: Domain

这个类封装了将错误和没有捕捉到的异常到有效对象功能。

域是EventEmitter的子类. 监听它的error事件来处理捕捉到的错误。

domain.run(fn)

  • fn {Function}

在域的上下文运行提供的函数,隐式的绑定了所有的事件分发器,计时器和底层请求。

这是使用域的基本方法。

例如:

var d = domain.create();d.on('error', function(er) {  console.error('Caught error!', er);});d.run(function() {  process.nextTick(function() {    setTimeout(function() { // simulating some various async stuff      fs.open('non-existent file', 'r', function(er, fd) {        if (er) throw er;        // proceed...      });    }, 100);  });});

这个例子里程序不会崩溃,而会触发d.on('error')

domain.members

  • {Array}

显式添加到域里的计时器和事件分发器数组。

domain.add(emitter)

  • emitter {EventEmitter | Timer} 添加到域里的计时器和事件分发器

显式地将一个分发器添加到域。如果分发器调用的事件处理函数抛出错误,或者分发器遇到error事件,将会导向域的error事件,和隐式绑定一样。

对于setIntervalsetTimeout返回的计时器同样适用。如果这些回调函数抛出错误,将会被域的'error'处理器捕捉到。

如果计时器或分发器已经绑定到域,那它将会从上一个域移除,绑定到当前域。

domain.remove(emitter)

  • emitter {EventEmitter | Timer} 要移除的分发器或计时器

与domain.add(emitter)函数恰恰相反,这个函数将分发器移除出域。

domain.bind(callback)

  • callback {Function} 回调函数
  • return: {Function}被绑定的函数

返回的函数是一个对于所提供的回调函数的包装函数。当调用这个返回的函数被时,所有被抛出的错误都会被导向到这个域的error事件。

Example

var d = domain.create();function readSomeFile(filename, cb) {  fs.readFile(filename, 'utf8', d.bind(function(er, data) {    // if this throws, it will also be passed to the domain    return cb(er, data ? JSON.parse(data) : null);  }));}d.on('error', function(er) {  // an error occurred somewhere.  // if we throw it now, it will crash the program  // with the normal line number and stack message.});

domain.intercept(callback)

  • callback {Function} 回调函数
  • return: {Function} 被拦截的函数

domain.bind(callback)类似。除了捕捉被抛出的错误外,它还会拦截Error对象作为参数传递到这个函数。

这种方式下,常见的if (er) return callback(er);模式,能被一个地方一个错误处理替换。

Example

var d = domain.create();function readSomeFile(filename, cb) {  fs.readFile(filename, 'utf8', d.intercept(function(data) {    // note, the first argument is never passed to the    // callback since it is assumed to be the 'Error' argument    // and thus intercepted by the domain.    // if this throws, it will also be passed to the domain    // so the error-handling logic can be moved to the 'error'    // event on the domain instead of being repeated throughout    // the program.    return cb(null, JSON.parse(data));  }));}d.on('error', function(er) {  // an error occurred somewhere.  // if we throw it now, it will crash the program  // with the normal line number and stack message.});

domain.enter()

这个函数就像runbindintercept的管道系统,它设置有效域。它设定了域的domain.activeprocess.domain,还隐式的将域推到域模块管理的域栈(关于域栈的细节详见domain.exit())。enter函数的调用,分隔了异步调用链以及绑定到一个域的I/O操作的结束或中断。

调用enter仅改变活动的域,而不改变域本身。在一个单独的域里可以调用任意多次Enterexit

domain.exit()

exit函数退出当前域,并从域的栈里移除。每当程序的执行流程要切换到不同的异步调用链的时候,要保证退出当前域。调用exit函数,分隔了异步调用链,和绑定到一个域的I/O操作的结束或中断。

如果有多个嵌套的域绑定到当前的上下文,exit函数将会退出所有嵌套。

调用exit仅改变活跃域,不会改变自身域。在一个单独的域里可以调用任意多次Enterexit

如果在这个域名下exit已经被设置,exit将不退出域返回。

domain.dispose()

稳定性: 0 - 抛弃。通过域里设置的错误事件来显示的消除失败的 IO 操作。

调用dispos后,通过run,bind或intercept绑定到域的回调函数不再使用这个域,并且分发dispose事件。


Stability: 3 - Stable

Node.js可以使用require('tls')来访问TLS/SSL模块:

const tls = require('tls');

tls模块使用OpenSSL来提供传输层安全性(Transport Layer Security,TLS)和安全套接层(Secure Socket Layer,SSL):加密过的流通讯。

TLS/SSL是一种公钥/私钥基础架构,每个客户端和服务端都需要一个私钥。私钥的创建方法如下:

openssl genrsa -out ryans-key.pem 2048

你还需要为所有服务器和某些客户端添加证书。证书由认证中心(Certificate Authority)签名,或者自签名。获得证书第一步是创建一个证书签名请求"Certificate Signing Request" (CSR)文件。证书的创建方法如下:

openssl req -new -sha256 -key ryans-key.pem -out ryans-csr.pem

使用CSR创建一个自签名的证书:

openssl x509 -req -in ryans-csr.pem -signkey ryans-key.pem -out ryans-cert.pem

或者你可以发送CSR给认证中心(Certificate Authority)来签名。

(TODO: 创建CA的文档,感兴趣的读者可以在Node源码test/fixtures/keys/Makefile里查看)

创建.pfx或.p12,可以这么做:

openssl pkcs12 -export -in agent5-cert.pem -inkey agent5-key.pem     -certfile ca-cert.pem -out agent5.pfx
  • in: 签名证书
  • inkey: 关联的私钥
  • certfile: 是将所有证书颁发机构 (CA) 证书连接到单个文件中,例如,cat ca1-cert.pem ca2-cert.pem > ca-cert.pem

协议支持

Node.js默认遵循SSLv2和SSLv3协议,不过这些协议被禁用。因为他们不太可靠,很容易受到威胁,参见CVE-2014-3566。某些情况下,旧版本客户端/服务器(比如 IE6)可能会产生问题。如果你想启用SSLv2或SSLv3 ,使用参数--enable-ssl2--enable-ssl3运行Node。Node.js的未来版本中不会再默认编译SSLv2 和SSLv3。

有一个办法可以强制node进入仅使用SSLv3或SSLv2模式,分别指定secureProtocol'SSLv3_method''SSLv2_method'

Node.js使用的默认协议方法准确名字是AutoNegotiate_method, 这个方法会尝试并协商客户端支持的从高到底协议。为了提供默认的安全级别,Node.js(v0.10.33 版本之后)通过将secureOptions设为SSL_OP_NO_SSLv3|SSL_OP_NO_SSLv2 ,明确的禁用了SSLv3和SSLv2(除非你给secureProtocol 传值--enable-ssl3,或--enable-ssl2,或SSLv3_method)。

如果你设置了secureOptions,我们不会重新这个参数。

改变这个行为的后果:

  • 如果你的应用被当做为安全服务器,SSLv3客户端不能协商建立连接,会被拒绝。这种情况下,你的服务器会触发clientError事件。错误消息会包含错误版本数字('wrong version number')。
  • 如果你的应用被当做安全客户端,和一个不支持比SSLv3更高安全性的方法的服务器通讯,你的连接不会协商成功。这种情况下,你的客户端会触发 clientError事件。错误消息会包含错误版本数字('wrong version number')。

Client-initiated renegotiation attack mitigation

TLS协议让客户端协商TLS会话的某些方法内容。但是,会话协商需要服务器端响应的资源,这回让它成为阻断服务攻击(denial-of-service attacks)的潜在媒介。

为了降低这种情况的发生,重新协商被限制为每10分钟3次。当超出这个界限时,在tls.TLSSocket实例上会触发错误。这个限制可设置:

  • tls.CLIENT_RENEG_LIMIT: 重新协商limit,默认是3。

  • tls.CLIENT_RENEG_WINDOW: 重新协商窗口的时间,单位秒,默认是10分钟。

除非你明确知道自己在干什么,否则不要改变默认值。

要测试你的服务器的话,使用openssl s_client -connect address:port连接服务器,并敲R<CR>(字母 R 键加回车)几次。

NPN 和 SNI

NPN(Next Protocol Negotiation 下次协议协商)和SNI (Server Name Indication 域名指示)都是TLS握手扩展:

  • NPN - 同一个TLS服务器使用多种协议 (HTTP, SPDY)
  • SNI - 同一个TLS服务器使用多个主机名(不同的 SSL 证书)。certificates.

完全正向保密

"Forward Secrecy"或"Perfect Forward Secrecy-完全正向保密"协议描述了秘钥协商(比如秘钥交换)方法的特点。实际上这意味着及时你的服务器的秘钥有危险,通讯仅有可能被一类人窃听,他们必须设法获的每次会话都会生成的秘钥对。

完全正向保密是通过每次握手时为秘钥协商随机生成密钥对来完成(和所有会话一个key相反)。实现这个技术(提供完全正向保密-Perfect Forward Secrecy)的方法被称为"ephemeral"。

通常目前有2个方法用于完成完全正向保密(Perfect Forward Secrecy):

  • DHE - 一个迪菲-赫尔曼密钥交换密钥协议(Diffie Hellman key-agreement protocol)短暂(ephemeral)版本。
  • ECDHE - 一个椭圆曲线密钥交换密钥协议( Elliptic Curve Diffie Hellman key-agreement protocol)短暂(ephemeral)版本。

短暂(ephemeral)方法有性能缺点,因为生成 key 非常耗费资源。

tls.getCiphers()

返回支持的SSL密码名数组。

例子:

var ciphers = tls.getCiphers();console.log(ciphers); // ['AES128-SHA', 'AES256-SHA', ...]

tls.createServer(options[, secureConnectionListener])

创建一个新的tls.Server。参数connectionListener会自动设置为secureConnection事件的监听器。参数options对象有以下可能性:

  • pfx: 包含私钥,证书和服务器的CA证书(PFX或PKCS12 格式)字符串或缓存Buffer。(keycertca互斥)。

  • key: 包含服务器私钥(PEM格式)字符串或缓存Buffer。(可以是keys的数组)(必传)。

  • passphrase: 私钥或pfx的密码字符串

  • cert: 包含服务器证书key(PEM格式)字符串或缓存Buffer。(可以是certs的数组)(必传)。

  • ca: 信任的证书(PEM格式)的字符串/缓存数组。如果忽略这个参数,将会使用"root" CAs ,比如VeriSign。用来授权连接。

  • crl : 不是PEM编码CRLs (证书撤销列表 Certificate Revocation List)的字符串就是字符串列表.

  • ciphers: 要使用或排除的密码(cipher)字符串

    为了减轻BEAST attacks ,推荐使用这个参数和之后会提到的honorCipherOrder参数来优化non-CBC密码(cipher)

    默认:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL。格式上更多细节参见OpenSSL cipher list format documentation

    ECDHE-RSA-AES128-SHA256, DHE-RSA-AES128-SHA256AES128-GCM-SHA256都是TLS v1.2密码(cipher),当node.js连接OpenSSL 1.0.1或更早版本(比如)时使用。注意,honorCipherOrder设置为enabled后,现在仍然可以和TLS v1.2客户端协商弱密码(cipher),

    RC4可作为客户端和老版本TLS协议通讯的备用方法。RC4这些年受到怀疑,任何对信任敏感的对象都会考虑其威胁性。国家级别(state-level)的参与者拥有中断它的能力。

    注意: 早些版本的修订建议,AES256-SHA作为可以接受的密码(cipher).Unfortunately,AES256-SHA是一个CBC密码(cipher),容易受到BEAST attacks 攻击,不要使用它。

  • ecdhCurve: 包含用来ECDH秘钥交换弧形(curve)名字符串,或者false禁用ECDH。

    默认prime256v1。更多细节参考RFC 4492

  • dhparam: DH参数文件,用于DHE秘钥协商。使用openssl dhparam命令来创建。如果加载文件失败,会悄悄的抛弃它。

  • handshakeTimeout: 如果SSL/TLS握手事件超过这个参数,会放弃里连接。默认是120秒.

    握手超时后,tls.Server对象会触发'clientError'事件。

  • honorCipherOrder: 当选择一个密码(cipher)时,使用服务器配置,而不是客户端的。

    虽然这个参数默认不可用,还是推荐你用这个参数,和ciphers参数连接使用,减轻BEAST攻击。

    注意,如果使用了SSLv2,服务器会发送自己的配置列表给客户端,客户端会挑选密码(cipher)。默认不支持SSLv2,除非node.js配置了./configure --with-sslv2

  • requestCert: 如果设为true,服务器会要求连接的客户端发送证书,并尝试验证证书。默认:false

  • rejectUnauthorized: 如果为true,服务器将会拒绝任何不被CAs列表授权的连接。仅requestCert参数为true时这个参数才有效。默认:false

  • checkServerIdentity(servername, cert): 提供一个重写的方法来检查证书对应的主机名。如果验证失败,返回error。如果验证通过,返回undefined

  • NPNProtocols: NPN协议的Buffer数组(协议需按优先级排序)。

  • SNICallback(servername, cb): 如果客户端支持SNI TLS扩展会调用这个函数。会传入2个参数:servernamecbSNICallback必须调用 cb(null, ctx),其中ctx是SecureContext实例。(你可以用tls.createSecureContext(...)来获取相应的SecureContext上下文)。如果 SNICallback没有提供,将会使用高级的API(参见下文).

  • sessionTimeout: 整数,设定了服务器创建TLS会话标示符(TLS session identifiers)和TLS会话票据(TLS session tickets)后的超时时间(单位:秒)。更多细节参见:SSL_CTX_set_timeout

  • ticketKeys: 一个48字节的Buffer实例,由16字节的前缀,16字节的hmac key,16字节的AES key组成。可用用它来接受tls服务器实例上的tls会话票据(tls session tickets)。

    注意: 自动在集群模块( cluster module)工作进程间共享。

  • sessionIdContext: 会话恢复(session resumption)的标示符字符串。如果requestCerttrue。默认值为命令行生成的MD5哈希值。否则不提供默认值。

  • secureProtocol: SSL使用的方法,例如,SSLv3_method强制SSL版本为3。可能的值定义于你所安装的OpenSSL中的常量SSL_METHODS

  • secureOptions: 设置服务器配置。例如设置SSL_OP_NO_SSLv3可用禁用SSLv3协议。所有可用的参数见SSL_CTX_set_options

响应服务器的简单例子:

var tls = require('tls');var fs = require('fs');var options = {  key: fs.readFileSync('server-key.pem'),  cert: fs.readFileSync('server-cert.pem'),  // This is necessary only if using the client certificate authentication.  requestCert: true,  // This is necessary only if the client uses the self-signed certificate.  ca: [ fs.readFileSync('client-cert.pem') ]};var server = tls.createServer(options, function(socket) {  console.log('server connected',              socket.authorized ? 'authorized' : 'unauthorized');  socket.write("welcome!
");  socket.setEncoding('utf8');  socket.pipe(socket);});server.listen(8000, function() {  console.log('server bound');});

或者:

var tls = require('tls');var fs = require('fs');var options = {  pfx: fs.readFileSync('server.pfx'),  // This is necessary only if using the client certificate authentication.  requestCert: true,};var server = tls.createServer(options, function(socket) {  console.log('server connected',              socket.authorized ? 'authorized' : 'unauthorized');  socket.write("welcome!
");  socket.setEncoding('utf8');  socket.pipe(socket);});server.listen(8000, function() {  console.log('server bound');});

你可以通过openssl s_client连接服务器来测试:

openssl s_client -connect 127.0.0.1:8000

tls.connect(options[, callback])

tls.connect(port[, host][, options][, callback])

创建一个新的客户端连接到指定的端口和主机(port and host)(老版本API),或者options.portoptions.host(如果忽略host,默认为 localhost)。options是一个包含以下值得对象:

  • host: 客户端需要连接到的主机。

  • port: 客户端需要连接到的端口。

  • socket: 在指定的socket(而非新建)上建立安全连接。如果这个参数有值,将忽略hostport参数。

  • path: 创建 到参数path的unix socket连接。如果这个参数有值,将忽略hostport参数。

  • pfx: 包含私钥,证书和客户端(PFX或PKCS12格式)的CA证书的字符串或Buffer缓存。

  • key: 包含客户端(PEM格式)的 私钥的字符串或Buffer缓存。可以是keys数组。

  • passphrase: 私钥或pfx的密码字符串。

  • cert: 包含客户端证书key(PEM格式)字符串或缓存Buffer。(可以是certs的数组)。

  • ca: 信任的证书(PEM格式)的字符串/缓存数组。如果忽略这个参数,将会使用"root" CAs ,比如VeriSign。用来授权连接。

  • rejectUnauthorized: 如果为true,服务器证书根据CAs列表授权列表验证。如果验证失败,触发'error'事件;err.code包含OpenSSL错误代码。默认:true

  • NPNProtocols: NPN协议的字符串或Buffer数组。Buffer必须有以下格式0x05hello0x05world,第一个字节是下一个协议名字的长度。(传的数组通常非常简单,比如:['hello', 'world'])。

  • servername: SNI(域名指示 Server Name Indication) TLS扩展的服务器名。

  • secureProtocol: SSL使用的方法,例如,SSLv3_method强制SSL版本为3。可能的值定义于你所安装的OpenSSL中的常量SSL_METHODS

  • session: 一个Buffer实例,包含TLS会话.

将参数callback添加到'secureConnect'事件上,其效果如同监听器。

tls.connect()返回一个tls.TLSSocket对象。

这是一个简单的客户端应答服务器例子:

var tls = require('tls');var fs = require('fs');var options = {  // These are necessary only if using the client certificate authentication  key: fs.readFileSync('client-key.pem'),  cert: fs.readFileSync('client-cert.pem'),  // This is necessary only if the server uses the self-signed certificate  ca: [ fs.readFileSync('server-cert.pem') ]};var socket = tls.connect(8000, options, function() {  console.log('client connected',              socket.authorized ? 'authorized' : 'unauthorized');  process.stdin.pipe(socket);  process.stdin.resume();});socket.setEncoding('utf8');socket.on('data', function(data) {  console.log(data);});socket.on('end', function() {  server.close();});

或者:

var tls = require('tls');var fs = require('fs');var options = {  pfx: fs.readFileSync('client.pfx')};var socket = tls.connect(8000, options, function() {  console.log('client connected',              socket.authorized ? 'authorized' : 'unauthorized');  process.stdin.pipe(socket);  process.stdin.resume();});socket.setEncoding('utf8');socket.on('data', function(data) {  console.log(data);});socket.on('end', function() {  server.close();});

类: tls.TLSSocket

net.Socket实例的封装,取代内部socket读写程序,执行透明的输入/输出数据的加密/解密。

new tls.TLSSocket(socket, options)

从现有的TCP socket里构造一个新的TLSSocket对象。

socket一个net.Socket的实例

options一个包含以下属性的对象:

  • secureContext: 来自tls.createSecureContext( ... )的可选TLS上下文对象。

  • isServer: 如果为true, TLS socket将会在服务器模式(server-mode)初始化。

  • server: 一个可选的net.Server实例

  • requestCert: 可选的,参见tls.createSecurePair

  • rejectUnauthorized: 可选的,参见tls.createSecurePair

  • NPNProtocols: 可选的,参见tls.createServer

  • SNICallback: 可选的,参见tls.createServer

  • session: 可选的,一个Buffer实例,包含TLS会话

  • requestOCSP: 可选的,如果为true- OCSP状态请求扩展将会被添加到客户端hello,并且OCSPResponse事件将会在socket上建立安全通讯前触发。

tls.createSecureContext(details)

创建一个凭证(credentials)对象,包含字典有以下的key:

  • pfx : 包含PFX或PKCS12加密的私钥,证书和服务器的CA证书(PFX或PKCS12格式)字符串或缓存Buffer。(keycertca互斥)。
  • key : 包含PEM加密过的私钥的字符串。
  • passphrase : 私钥或pfx的密码字符串。
  • cert : 包含PEM加密过的证书的字符串。
  • ca : 信任的PEM加密过的可信任的证书(PEM格式)字符串/缓存数组。
  • crl :PEM加密过的CRLs(证书撤销列表)
  • ciphers: 要使用或排除的密码(cipher)字符串。更多格式上的细节参见http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT
  • honorCipherOrder : 当选择一个密码(cipher) 时, 使用服务器配置,而不是客户端的。 更多细节参见tls模块文档。

如果没给 'ca' 细节,node.js 将会使用默认的公开信任的 CAs 列表(参见http://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt)。

tls.createSecurePair([context][, isServer][, requestCert][, rejectUnauthorized])

创建一个新的安全对(secure pair)对象,包含2个流,其中一个读/写加密过的数据,另外一个读/写明文数据。通常加密端数据来自是从输入的加密数据流,另一端被当做初始加密流。

  • credentials: 来自tls.createSecureContext( ... )的安全上下文对象。

  • isServer: 是否以服务器/客户端模式打开这个tls连接。

  • requestCert: 是否服务器需要连接的客户端发送证书。仅适用于服务端连接。

  • rejectUnauthorized:非法证书时,是否服务器需要自动拒绝客户端。启用requestCert后,才适用于服务器。

tls.createSecurePair()返回一个安全对(SecurePair)对象,包含明文cleartext和密文encrypted流 。

注意: cleartexttls.TLSSocket拥有相同的 API。

类: SecurePair

通过tls.createSecurePair返回。

事件: 'secure'

一旦安全对(SecurePair)成功建立一个安全连接,安全对(SecurePair)将会触发这个事件。

和检查服务器'secureConnection'事件一样,pair.cleartext.authorized必须检查确认是否适用的证书是授权过的。

类: tls.Server

这是net.Server的子类,拥有相同的方法。这个类接受适用TLS或SSL的加密连接,而不是接受原始TCP连接。

事件: 'secureConnection'

function (tlsSocket) {}

新的连接握手成功后回触发这个事件。参数是tls.TLSSocket实例。它拥有常用的流方法和事件。

socket.authorized是否客户端被证书(服务器提供)授权。如果socket.authorized为false,socket.authorizationError是如何授权失败。值得一提的是,依赖于TLS服务器的设置,你的未授权连接可能也会被接受。socket.authorizationError如何授权失败。值得一提的是,依赖于TLS服务器的设置,你的未授权连接可能也会被接受。socket.npnProtocol包含选择的NPN协议的字符串。socket.servername包含SNI请求的服务器名的字符串。

事件: 'clientError'

function (exception, tlsSocket) { }

在安全连接建立前,客户端连接触发'error'事件会转发到这里来。

tlsSockettls.TLSSocket,错误是从这里触发的。

事件: 'newSession'

function (sessionId, sessionData, callback) { }

创建TLS会话的时候会触发。可能用来在外部存储器里存储会话。callback必须最后调用,否则没法从安全连接发送/接收数据。

注意: 添加这个事件监听器仅会在连接连接时有效果。

事件: 'resumeSession'

function (sessionId, callback) { }

当客户端想恢复之前的TLS会话时会触发。事件监听器可能会使用sessionId到外部存储器里查找,一旦结束会触发callback(null, sessionData)。如果会话不能恢复(比如不存在这个会话),可能会调用callback(null, null)。调用callback(err)将会终止连接,并销毁socket。

注意: 添加这个事件监听器仅会在连接连接时有效果。

事件: 'OCSPRequest'

function (certificate, issuer, callback) { }

当客户端发送证书状态请求时会触发。你可以解析服务器当前的证书,来获取OCSP网址和证书id,获取OCSP响应调用callback(null, resp),其中respBuffer实例。证书(certificate)和发行者(issuer)都是初级表达式缓存(BufferDER-representations of the primary)和证书的发行者。它可以用来获取OCSP证书和OCSP终点网址。

可以调用callback(null, null),表示没有OCSP响应。

调用callback(err)可能会导致调用socket.destroy(err)

典型流程:

  1. 客户端连接服务器,并发送OCSPRequest(通过 ClientHello 里的状态信息扩展)。
  2. 服务器收到请求,调用OCSPRequest事件监听器。
  3. 服务器从certificateissuer获取OCSP网址,并执行OCSP request到CA
  4. 服务器CA收到OCSPResponse, 并通过callback参数送回到客户端
  5. 客户端验证响应,并销毁socket或执行握手

注意: 如果证书是自签名的,或者如果发行者不再根证书列表里(你可以通过参数提供一个发行者)。issuer就可能为null。

注意:添加这个事件监听器仅会在连接连接时有效果。

注意:你可以能想要使用npm模块(比如asn1.js)来解析证书。

server.listen(port[, host][, callback])

在指定的端口和主机上开始接收连接。如果host参数没传,服务接受通过IPv4地址(INADDR_ANY)的直连。

这是异步函数。当服务器已经绑定后回调用最后一个参数callback

更多信息参见net.Server

server.close()

停止服务器,不再接收新连接。这是异步函数,当服务器触发'close'事件后回最终关闭。

server.address()

返回绑定的地址,地址家族名和服务器端口。更多信息参见net.Server.address()

server.addContext(hostname, context)

如果客户端请求SNI主机名和传入的hostname相匹配,将会用到安全上下文(secure context)。context可以包含keycertca和/或tls.createSecureContextoptions参数的其他任何属性。

server.maxConnections

设置这个属性可以在服务器的连接数达到最大值时拒绝连接。

server.connections

当前服务器连接数。

类: CryptoStream

稳定性: 0 - 抛弃. 使用 tls.TLSSocket 替代.

这是一个加密的流

cryptoStream.bytesWritten

底层socket写字节访问器(bytesWritten accessor)的代理,将会返回写到socket的全部字节数。包括 TLS 的开销。

类: tls.TLSSocket

net.Socket实例的封装,透明的加密写数据和所有必须的TLS协商。

这个接口实现了一个双工流接口。它包含所有常用的流方法和事件。

事件: 'secureConnect'

新的连接成功握手后回触发这个事件。无论服务器证书是否授权,都会调用监听器。用于用户测试tlsSocket.authorized看看如果服务器证书已经被指定的CAs签名。如果tlsSocket.authorized === false ,可以在tlsSocket.authorizationError里找到错误。如果使用了NPN,你可以检tlsSocket.npnProtocol获取协商协议(negotiated protocol)。

事件: 'OCSPResponse'

function (response) { }

如果启用requestOCSP参赛会触发这个事件。response是缓存对象,包含服务器的OCSP响应。

一般来说,response是服务器CA签名的对象,它包含服务器撤销证书状态的信息。

tlsSocket.encrypted

静态boolean变量,一直是true。可以用来区别TLS socket和常规对象。

tlsSocket.authorized

boolean变量,如果对等实体证书(peer's certificate)被指定的某个CAs签名,返回true,否则false

tlsSocket.authorizationError

对等实体证书(peer's certificate)没有验证通过的原因。当tlsSocket.authorized === false时,这个属性才可用。

tlsSocket.getPeerCertificate([ detailed ])

返回一个代表对等实体证书( peer's certificate)的对象。这个返回对象有一些属性和证书内容相对应。如果参数detailedtrue,将会返回包含发行者issuer完整链。如果false,仅有顶级证书没有发行者issuer属性。

例子:

{ subject:   { C: 'UK',     ST: 'Acknack Ltd',     L: 'Rhys Jones',     O: 'node.js',     OU: 'Test TLS Certificate',     CN: 'localhost' },  issuerInfo:   { C: 'UK',     ST: 'Acknack Ltd',     L: 'Rhys Jones',     O: 'node.js',     OU: 'Test TLS Certificate',     CN: 'localhost' },  issuer:   { ... another certificate ... },  raw: < RAW DER buffer >,  valid_from: 'Nov 11 09:52:22 2009 GMT',  valid_to: 'Nov  6 09:52:22 2029 GMT',  fingerprint: '2A:7A:C2:DD:E5:F9:CC:53:72:35:99:7A:02:5A:71:38:52:EC:8A:DF',  serialNumber: 'B9B0D332A1AA5635' }

如果peer没有提供证书,返回null或空对象。

tlsSocket.getCipher()

返回一个对象,它代表了密码名和当前连接的SSL/TLS协议的版本。

例子:{ name: 'AES256-SHA', version: 'TLSv1/SSLv3' }

更多信息参见http://www.openssl.org/docs/ssl/ssl.html#DEALING_WITH_CIPHERS里的SSL_CIPHER_get_name()和SSL_CIPHER_get_version() 。

tlsSocket.renegotiate(options, callback)

初始化 TLS 重新协商进程。参数options可能包含以下内容:rejectUnauthorizedrequestCert(细节参见tls.createServer)。一旦重新协商成(renegotiation)功完成,将会执行callback(err),其中errnull

注意:当安全连接建立后,可以用这来请求对等实体证书(peer's certificate)。

注意:作为服务器运行时,handshakeTimeout超时后,socket将会被销毁。

tlsSocket.setMaxSendFragment(size)

设置最大的TLS碎片大小(默认最大值为:16384,最小值为:512)。成功的话,返回true,否则返回false

小的碎片包会减少客户端的缓存延迟:大的碎片直到接收完毕后才能被TLS层完全缓存,并且验证过完整性;大的碎片可能会有多次往返,并且可能会因为丢包或重新排序导致延迟。而小的碎片会增加额外的TLS帧字节和CPU负载,这会减少CPU的吞吐量。

tlsSocket.getSession()

返回ASN.1编码的TLS会话,如果没有协商,会返回。连接到服务器时,可以用来加速握手的建立。

tlsSocket.getTLSTicket()

注意:仅和客户端TLS socket打交道。仅在调试时有用,会话重用是,提供session参数给tls.connect

返回TLS会话票据(ticket),或如果没有协商(negotiated),返回undefined

tlsSocket.address()

返回绑定的地址,地址家族名和服务器端口。更多信息参见net.Server.address()。返回三个属性, 比如:{ port: 12346, family: 'IPv4', address: '127.0.0.1' }

tlsSocket.remoteAddress

表示远程IP地址(字符串表示),例如:'74.125.127.100''2001:4860:a005::68'.

tlsSocket.remoteFamily

表示远程 IP 家族,'IPv4''IPv6'

tlsSocket.remotePort

远程端口(数字表示),例如,443

tlsSocket.localAddress

本地IP地址(字符串表示)。

tlsSocket.localPort

本地端口号(数字表示)。


进程

本节介绍Node.js的process(过程)对象,它提供有关当前Node.js过程的信息和控制。

process是全局对象,能够在任意位置对其进行访问,而无需使用require(),是EventEmitter的实例。

退出状态码

当不需要处理新的异步的操作时,Node正常情况下退出时会返回状态码0。下面提供了一些表示其他状态的状态码:

  • 1未捕获的致命异常-Uncaught Fatal Exception - 有未捕获异常,并且没有被域或 uncaughtException 处理函数处理。
  • 2- Unused (保留)
  • 3JavaScript解析错误-Internal JavaScript Parse Error - JavaScript的源码启动 Node 进程时引起解析错误。非常罕见,仅会在开发Node时才会有。
  • 4JavaScript评估失败-Internal JavaScript Evaluation Failure - JavaScript的源码启动Node进程,评估时返回函数失败。非常罕见,仅会在开发 Node时才会有。
  • 5致命错误-Fatal Error - V8里致命的不可恢复的错误。通常会打印到stderr,内容为:FATAL ERROR
  • 6Non-function异常处理-Non-function Internal Exception Handler - 未捕获异常,内部异常处理函数不知为何设置为on-function,并且不能被调用。
  • 7异常处理函数运行时失败-Internal Exception Handler Run-Time Failure - 未捕获的异常, 并且异常处理函数处理时自己抛出了异常。例如,如果 process.on('uncaughtException')domain.on('error')抛出了异常。
  • 8- Unused保留。 之前版本的Node, 状态码8有时表示未捕获异常。
  • 9- 参数非法-Invalid Argument - 可能是给了未知的参数,或者给的参数没有值。
  • 10运行时失败-Internal JavaScript Run-Time Failure - JavaScript的源码启动 Node 进程时抛出错误,非常罕见,仅会在开发 Node 时才会有。
  • 12无效的Debug参数-Invalid Debug Argument - 设置了参数--debug--debug-brk,但是选择了错误端口。
  • >128信号退出-Signal Exits - 如果Node接收到致命信号,比如SIGKILLSIGHUP,那么退出代码就是128加信号代码。这是标准的Unix做法,退出信号代码放在高位。

事件: 'exit'

当进程准备退出时触发。此时已经没有办法阻止从事件循环中推出。因此,你必须在处理函数中执行同步操作。这是一个在固定事件检查模块状态(比如单元测试)的好时机。回调函数有一个参数,它是进程的退出代码。

监听exit事件的例子:

process.on('exit', function(code) {  // do *NOT* do this  setTimeout(function() {    console.log('This will not run');  }, 0);  console.log('About to exit with code:', code);});

事件: 'beforeExit'

当node清空事件循环,并且没有其他安排时触发这个事件。通常来说,当没有进程安排时node退出,但是'beforeExit'的监听器可以异步调用,这样node就会继续执行。

'beforeExit'并不是明确退出的条件,process.exit()或异常捕获才是,所以不要把它当做'exit'事件。除非你想安排更多的工作。

事件: 'uncaughtException'

当一个异常冒泡回到事件循环,触发这个事件。如果给异常添加了监视器,默认的操作(打印堆栈跟踪信息并退出)就不会发生。

监听uncaughtException的例子:

process.on('uncaughtException', function(err) {  console.log('Caught exception: ' + err);});setTimeout(function() {  console.log('This will still run.');}, 500);// Intentionally cause an exception, but don't catch it.nonexistentFunc();console.log('This will not run.');

注意:uncaughtException是非常简略的异常处理机制。

尽量不要使用它,而应该用domains 。如果你用了,每次未处理异常后,重启你的程序。

不要使用node.js里诸如On Error Resume Next这样操作。每个未处理的异常意味着你的程序,和你的node.js扩展程序,一个未知状态。盲目的恢复意味着任何事情都可能发生

你在升级的系统时拉掉了电源线,然后恢复了。可能10次里有9次没有问题,但是第10次,你的系统可能就会挂掉。

Signal 事件

当进程接收到信号时就触发。信号列表详见标准的POSIX信号名,如SIGINT、SIGUSR1等。

监听SIGINT的例子:

// Start reading from stdin so we don't exit.process.stdin.resume();process.on('SIGINT', function() {  console.log('Got SIGINT.  Press Control-D to exit.');});

在大多数终端程序里,发送SIGINT信号的简单方法是按下信号Control-C

注意:

  • SIGUSR1node.js 接收这个信号开启调试模式。可以安装一个监听器,但开始时不会中断调试。
  • SIGTERMSIGINT在非Windows系统里,有默认的处理函数,退出(伴随退出代码128 + 信号码)前,重置退出模式。如果这些信号有监视器,默认的行为将会被移除。
  • SIGPIPE默认情况下忽略,可以加监听器。
  • SIGHUP当Windowns控制台关闭的时候生成,其他平台的类似条件,参见signal(7)。可以添加监听者,Windows平台上10秒后会无条件退出。在非Windows平台上,SIGHUP的默认操作是终止node,但是一旦添加了监听器,默认动作将会被移除。
  • SIGTERMWindows不支持,可以被监听。
  • SIGINT所有的终端都支持,通常由CTRL+C生成(可能需要配置)。当终端原始模式启用后不会再生成。
  • SIGBREAKWindows里,按下CTRL+BREAK会发送。非Windows平台,可以被监听,但是不能发送或生成。
  • SIGWINCH- 当控制台被重设大小时发送。Windows系统里,仅会在控制台上输入内容时,光标移动,或者可读的tty在原始模式上使用。
  • SIGKILL不能有监视器,在所有平台上无条件关闭node。
  • SIGSTOP不能有监视器。

Windows不支持发送信号,但是node提供了很多process.kill()child_process.kill()的模拟:

  • 发送Sending信号0可以查找运行中得进程
  • 发送SIGINTSIGTERMSIGKILL会引起目标进程无条件退出。

process.stdout

一个Writable Stream执向stdout(fd1).

例如:console.log的定义:

console.log = function(d) {  process.stdout.write(d + '
');};

process.stderrprocess.stdout和 node 里的其他流不同,他们不会被关闭(end()将会被抛出),它们不会触发finish事件,并且写是阻塞的。

  • 引用指向常规文件或TTY文件描述符时,是阻塞的。
  • 引用指向pipe管道时:
    • 在Linux/Unix里阻塞.
    • 在Windows像其他流一样,不被阻塞

检查Node是否运行在TTY上下文中,从process.stderrprocess.stdoutprocess.stdin里读取isTTY属性。

$ node -p "Boolean(process.stdin.isTTY)"true$ echo "foo" | node -p "Boolean(process.stdin.isTTY)"false$ node -p "Boolean(process.stdout.isTTY)"true$ node -p "Boolean(process.stdout.isTTY)" | catfalse

更多信息参见the tty docs

process.stderr

一个指向stderr (fd2)的可写流。

process.stderrprocess.stdout和node里的其他流不同,他们不会被关闭(end()将会被抛出),它们不会触发finish事件,并且写是阻塞的。

  • 引用指向常规文件或TTY文件描述符时,是阻塞的。
  • 引用指向pipe管道时:
    • 在Linux/Unix里阻塞
    • 在Windows像其他流一样,不被阻塞

process.stdin

一个指向stdin (fd0)的可读流。

以下例子:打开标准输入流,并监听两个事件:

process.stdin.setEncoding('utf8');process.stdin.on('readable', function() {  var chunk = process.stdin.read();  if (chunk !== null) {    process.stdout.write('data: ' + chunk);  }});process.stdin.on('end', function() {  process.stdout.write('end');});

process.stdin可以工作在老模式里,和v0.10之前版本的node代码兼容。

更多信息参见Stream compatibility.

在老的流模式里,stdin流默认暂停,必须调用process.stdin.resume()读取。可以调用process.stdin.resume()切换到老的模式。

如果开始一个新的工程,最好选择新的流,而不是用老的流。

process.argv

包含命令行参数的数组。第一个元素是'node',第二个参数是JavaScript文件的名字,第三个参数是任意的命令行参数。

// print process.argvprocess.argv.forEach(function(val, index, array) {  console.log(index + ': ' + val);});

将会生成:

$ node process-2.js one two=three four0: node1: /Users/mjr/work/node/process-2.js2: one3: two=three4: four

process.execPath

开启当前进程的执行文件的绝对路径。

例子:

/usr/local/bin/node

process.execArgv

启动进程所需的 node 命令行参数。这些参数不会在process.argv里出现,并且不包含node执行文件的名字,或者任何在名字之后的参数。这些用来生成子进程,使之拥有和父进程有相同的参数。

例子:

$ node --harmony script.js --version

process.execArgv 的参数:

['--harmony']

process.argv 的参数:

['/usr/local/bin/node', 'script.js', '--version']

process.abort()

这将导致node触发abort事件。会让node退出并生成一个核心文件。

process.chdir(directory)

改变当前工作进程的目录,如果操作失败抛出异常。

console.log('Starting directory: ' + process.cwd());try {  process.chdir('/tmp');  console.log('New directory: ' + process.cwd());}catch (err) {  console.log('chdir: ' + err);}

process.cwd()

返回当前进程的工作目录:

console.log('Current directory: ' + process.cwd());

process.env

包含用户环境的对象,参见environ(7).

这个对象的例子:

{ TERM: 'xterm-256color',  SHELL: '/usr/local/bin/bash',  USER: 'maciej',  PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin',  PWD: '/Users/maciej',  EDITOR: 'vim',  SHLVL: '1',  HOME: '/Users/maciej',  LOGNAME: 'maciej',  _: '/usr/local/bin/node' }

你可以写入这个对象,但是不会改变当前运行的进程。以下的命令不会成功:

node -e 'process.env.foo = "bar"' && echo $foo

这个会成功:

process.env.foo = 'bar';console.log(process.env.foo);

process.exit([code])

使用指定的code结束进程。如果忽略,将会使用code 0

使用失败的代码退出:

process.exit(1);

Shell将会看到退出代码为1.

process.exitCode

进程退出时的代码,如果进程优雅的退出,或者通过process.exit()退出,不需要指定退出码。

设定process.exit(code)将会重写之前设置的process.exitCode

process.getgid()

注意:这个函数仅在POSIX平台上可用(例如,非Windows和Android)。

获取进程的群组标识(参见getgid(2))。获取到得时群组的数字id,而不是名字。

if (process.getgid) {  console.log('Current gid: ' + process.getgid());}

process.setgid(id)

注意:这个函数仅在POSIX平台上可用(例如,非Windows 和 Android)。

设置进程的群组标识(参见setgid(2))。可以接收数字ID或者群组名。如果指定了群组名,会阻塞等待解析为数字ID 。

if (process.getgid && process.setgid) {  console.log('Current gid: ' + process.getgid());  try {    process.setgid(501);    console.log('New gid: ' + process.getgid());  }  catch (err) {    console.log('Failed to set gid: ' + err);  }}

process.getuid()

注意:这个函数仅在POSIX平台上可用(例如,非Windows和Android)。

获取进程的用户标识(参见getuid(2))。这是数字的用户id,不是用户名:

if (process.getuid) {  console.log('Current uid: ' + process.getuid());}

process.setuid(id)

注意:这个函数仅在POSIX平台上可用(例如,非Windows和Android)。

设置进程的用户标识(参见setuid(2))。接收数字ID或字符串名字。果指定了群组名,会阻塞等待解析为数字ID。

if (process.getuid && process.setuid) {  console.log('Current uid: ' + process.getuid());  try {    process.setuid(501);    console.log('New uid: ' + process.getuid());  }  catch (err) {    console.log('Failed to set uid: ' + err);  }}

process.getgroups()

注意:这个函数仅在POSIX平台上可用(例如,非Windows和Android)。

返回进程的群组iD数组。POSIX系统没有保证一定有,但是node.js保证有。

process.setgroups(groups)

注意:这个函数仅在POSIX平台上可用(例如,非Windows和Android)。

设置进程的群组ID。这是授权操作,所有你需要有root权限,或者有CAP_SETGID能力。

列表可以包含群组IDs,群组名,或者两者都有。

process.initgroups(user, extra_group)

注意:这个函数仅在POSIX平台上可用(例如,非Windows和Android)。

读取/etc/group ,并初始化群组访问列表,使用成员所在的所有群组。这是授权操作,所有你需要有root权限,或者有CAP_SETGID能力。

user是用户名或者用户ID,extra_group是群组名或群组ID。

当你在注销权限 (dropping privileges) 的时候需要注意. 例子:

console.log(process.getgroups());         // [ 0 ]process.initgroups('bnoordhuis', 1000);   // switch userconsole.log(process.getgroups());         // [ 27, 30, 46, 1000, 0 ]process.setgid(1000);                     // drop root gidconsole.log(process.getgroups());         // [ 27, 30, 46, 1000 ]

process.version

一个编译属性,包含NODE_VERSION

console.log('Version: ' + process.version);

process.versions

一个属性,包含了node的版本和依赖。

console.log(process.versions);

打印出来:

{ http_parser: '1.0',  node: '0.10.4',  v8: '3.14.5.8',  ares: '1.9.0-DEV',  uv: '0.10.3',  zlib: '1.2.3',  modules: '11',  openssl: '1.0.1e' }

process.config

一个包含用来编译当前node执行文件的javascript配置选项的对象。它与运行./configure 脚本生成的"config.gypi"文件相同。

一个可能的输出:

{ target_defaults:   { cflags: [],     default_configuration: 'Release',     defines: [],     include_dirs: [],     libraries: [] },  variables:   { host_arch: 'x64',     node_install_npm: 'true',     node_prefix: '',     node_shared_cares: 'false',     node_shared_http_parser: 'false',     node_shared_libuv: 'false',     node_shared_v8: 'false',     node_shared_zlib: 'false',     node_use_dtrace: 'false',     node_use_openssl: 'true',     node_shared_openssl: 'false',     strict_aliasing: 'true',     target_arch: 'x64',     v8_use_snapshot: 'true' } }

process.kill(pid[, signal])

发送信号给进程。pid是进程id,并且signal是发送的信号的字符串描述。信号名是字符串,比如'SIGINT'或'SIGHUP'。如果忽略,信号会是'SIGTERM'。更多信息参见Signal 事件和kill(2) .

如果进程没有退出,会抛出错误。信号0可以用来测试进程是否存在。

注意,虽然这个这个函数名叫process.kill,它真的仅是信号发射器,就像kill系统调用。信号发射可以做其他事情,不仅是杀死目标进程。

例子, 给自己发信号:

process.on('SIGHUP', function() {  console.log('Got SIGHUP signal.');});setTimeout(function() {  console.log('Exiting.');  process.exit(0);}, 100);process.kill(process.pid, 'SIGHUP');

注意:当Node.js接收到SIGUSR1信号,它会开启debugger调试模式, 参见Signal Events.

process.pid

当前进程的PID:

console.log('This process is pid ' + process.pid);

process.title

获取/设置(Getter/setter) 'ps'中显示的进程名。

使用setter时,字符串的长度由系统指定,可能会很短。

在Linux和OS X上,它受限于名称的长度加上命令行参数的长度,因为它会覆盖参数内存(argv memory)。

v0.8版本允许更长的进程标题字符串,也支持覆盖环境内存,但是存在潜在的不安全和混乱(很难说清楚)。

process.arch

当前CPU的架构:'arm'、'ia32'或者'x64'。

console.log('This processor architecture is ' + process.arch);

process.platform

运行程序所在的平台系统'darwin''freebsd''linux''sunos'或者'win32'

console.log('This platform is ' + process.platform);

process.memoryUsage()

返回一个对象,描述了Node进程所用的内存状况,单位为字节。

var util = require('util');console.log(util.inspect(process.memoryUsage()));

将会生成:

{ rss: 4935680,  heapTotal: 1826816,  heapUsed: 650472 }

heapTotalheapUsed指V8的内存使用情况。

process.nextTick(callback)

  • callback{Function}

一旦当前事件循环结束,调用回到函数。

这不是setTimeout(fn, 0)的简单别名,这个效率更高。在任何附加I/O事件在子队列事件循环中触发前,它就会运行。

console.log('start');process.nextTick(function() {  console.log('nextTick callback');});console.log('scheduled');// Output:// start// scheduled// nextTick callback

在对象构造后,在I/O事件发生前,你又想改变附加事件处理函数时,这个非常有用。

function MyThing(options) {  this.setupOptions(options);  process.nextTick(function() {    this.startDoingStuff();  }.bind(this));}var thing = new MyThing();thing.getReadyForStuff();// thing.startDoingStuff() gets called now, not before.

要保证你的函数一定是100%同步执行,或者100%异步执行。例子:

// WARNING!  DO NOT USE!  BAD UNSAFE HAZARD!function maybeSync(arg, cb) {  if (arg) {    cb();    return;  }  fs.stat('file', cb);}

这个API非常危险. 如果你这么做:

maybeSync(true, function() {  foo();});bar();

不清楚foo()bar()哪个先执行。

更好的方法:

function definitelyAsync(arg, cb) {  if (arg) {    process.nextTick(cb);    return;  }  fs.stat('file', cb);}

注意:nextTick队列会在完全执行完毕之后才调用I/O操作。因此,递归设置nextTick的回调就像一个while(true);循环一样,将会阻止任何I/O操作的发生。

process.umask([mask])

设置或读取进程文件的掩码。子进程从父进程继承掩码。如果mask参数有效,返回旧的掩码。否则,返回当前掩码。

var oldmask, newmask = 0022;oldmask = process.umask(newmask);console.log('Changed umask from: ' + oldmask.toString(8) +            ' to ' + newmask.toString(8));

process.uptime()

返回Node已经运行的秒数。

process.hrtime()

返回当前进程的高分辨时间,形式为[seconds, nanoseconds]数组。它是相对于过去的任意事件。该值与日期无关,因此不受时钟漂移的影响。主要用途是可以通过精确的时间间隔,来衡量程序的性能。

你可以将之前的结果传递给当前的process.hrtime(),会返回两者间的时间差,用来基准和测量时间间隔。

var time = process.hrtime();// [ 1800216, 25 ]setTimeout(function() {  var diff = process.hrtime(time);  // [ 1, 552 ]  console.log('benchmark took %d nanoseconds', diff[0] * 1e9 + diff[1]);  // benchmark took 1000000527 nanoseconds}, 1000);

process.mainModule

require.main的备选方法。不同点,如果主模块在运行时改变,require.main可能会继续返回老的模块。可以认为,这两者引用了同一个模块。

process.mainModule属性提供了一种替代的检索方式require.main。不同之处在于,如果主模块在运行时发生更改,require.main可能仍会引用发生更改之前所需模块中的原始主模块。通常,假设这两个参照相同的模块是安全的。

require.main一样, 如果没有入口脚本,将会返回undefined

稳定性: 2 - 不稳定

Node.js的tty模块包含tty.ReadStreamtty.WriteStream类,多数情况下,你不必直接使用这个模块,访问该模块的方法如下:

const tty = require('tty');

当node检测到自己正运行于TTY上下文时,process.stdin将会是一个tty.ReadStream实例,并且process.stdout将会是tty.WriteStream实例。检测 node是否运行在TTY上下文的好方法是检测process.stdout.isTTY

$ node -p -e "Boolean(process.stdout.isTTY)"true$ node -p -e "Boolean(process.stdout.isTTY)" | catfalse

tty.isatty(fd)

如果fd和终端相关联返回true,否则返回false

tty.setRawMode(mode)

已经抛弃。使用tty.ReadStream#setRawMode()(比如process.stdin.setRawMode())替换。

Class: ReadStream

net.Socket的子类,表示tty的可读部分。通常情况,在任何node程序里(仅当isatty(0)为true时),process.stdintty.ReadStream的唯一实例。

rs.isRaw

Boolean值,默认为false。它代表当前tty.ReadStream实例的"raw"状态。

rs.setRawMode(mode)

mode需是truefalse。它设定tty.ReadStream属性为原始设备或默认。isRaw将会设置为结果模式。

Class: WriteStream

net.Socket的子类,代表tty的可写部分。通常情况下,process.stdouttty.WriteStream唯一实例(仅当isatty(1)为true时)。

ws.columns

TTY当前拥有的列数。触发"resize"事件时会更新这个值。

ws.rows

TTY当前拥有的行数。触发"resize"事件时会更新这个值。

Event: 'resize'

function () {}

行或列变化时会触发refreshSize()事件。

process.stdout.on('resize', function() {  console.log('screen size has changed!');  console.log(process.stdout.columns + 'x' + process.stdout.rows);});


稳定性: 3 - 稳定

Node.js的dgram模块提供了UDP数据报套接字的实现。

使用数据报文sockets(Datagram sockets)的方式是调用require('dgram')

重要提醒:dgram.Socket#bind()的行为在v0.10做了改动 ,它总是异步的。如果你的代码像下面的一样:

var s = dgram.createSocket('udp4');s.bind(1234);s.addMembership('224.0.0.114');

现在需要改为:

var s = dgram.createSocket('udp4');s.bind(1234, function() {  s.addMembership('224.0.0.114');});

dgram.createSocket(type[, callback])

  • type字符串。 'udp4'或'udp6'
  • callback函数。附加到message事件的监听器。可选参数。
  • 返回:Socket对象

创建指定类型的数据报文(datagram) Socket。有效类型是udp4udp6

接受一个可选的回调,会被添加为message的监听事件。

如果你想接收数据报文(datagram)可以调用socket.bind()socket.bind()将会绑定到所有接口("all interfaces")的随机端口上(udp4udp6 sockets都适用)。你可以通过socket.address().addresssocket.address().port获取地址和端口。

dgram.createSocket(options[, callback])

  • options对象
  • callback函数。给message事件添加事件监听器。
  • 返回:Socket对象

参数options必须包含type值(udp4udp6),或可选的boolean值reuseAddr

reuseAddr为 true 时,socket.bind()将会重用地址,即使另一个进程已经绑定socket。reuseAddr默认为false

回调函数为可选参数,作为message事件的监听器。

如果你想接受数据报文(datagram),可以调用socket.bind()socket.bind()将会绑定到所有接口("all interfaces")地址的随机端口上(udp4udp6 sockets都适用)。你可以通过socket.address().addresssocket.address().port获取地址和端口。

Class: dgram.Socket

报文数据Socket类封装了数据报文(datagram)函数。必须通过dgram.createSocket(...)函数创建。

Event: 'message'

  • msg缓存对象. 消息。
  • rinfo对象. 远程地址信息。

当socket上新的数据报文(datagram)可用的时候,会触发这个事件。msg是一个缓存,rinfo是一个包含发送者地址信息的对象。

socket.on('message', function(msg, rinfo) {  console.log('Received %d bytes from %s:%d
',              msg.length, rinfo.address, rinfo.port);});

Event: 'listening'

当socket开始监听数据报文(datagram)时触发。在UDP socket创建时触发。

Event: 'close'

当socket使用close()关闭时触发。在这个socket上不会触发新的消息事件。

Event: 'error'

  • exceptionError对象

当发生错误时触发。

socket.send(buf, offset, length, port, address[, callback])

  • buf缓存对象或字符串. 要发送的消息。
  • offset整数。消息在缓存中得偏移量。
  • length整数。消息的比特数。
  • port整数。端口的描述。
  • address字符串。目标的主机名或IP地址。
  • callback函数。当消息发送完毕的时候调用。可选。

对于UDP socket,必须指定目标端口和地址。address参数可能是字符串,它会被DNS解析。

如果忽略地址或者地址是空字符串,将使用'0.0.0.0''::0'替代。依赖于网络配置,这些默认值有可能行也可能不行。

如果socket之前没被调用bind绑定,则它会被分配一个随机端口并绑定到所有接口("all interfaces")地址(udp4sockets的'0.0.0.0' ,udp6sockets的'::0')

回调函数可能用来检测DNS错误,或用来确定什么时候重用buf对象。注意,DNS查询会导致发送tick延迟。通过回调函数能确认数据报文(datagram)是否已经发送的。

考虑到多字节字符串情况,偏移量和长度是字节长度byte length,而不是字符串长度。

下面的例子是在localhost上发送一个UDP包给随机端口:

var dgram = require('dgram');var message = new Buffer("Some bytes");var client = dgram.createSocket("udp4");client.send(message, 0, message.length, 41234, "localhost", function(err) {  client.close();});

关于UDP数据报文(datagram) 尺寸

IPv4/v6数据报文(datagram)的最大长度依赖于MTU (Maximum Transmission Unit)和Payload Length的长度。

  • Payload Length内容为16位宽,它意味着Payload的最大字节说不超过64k,其中包括了头信息和数据(65,507字节 = 65,535 − 8字节UDP头 − 20字节IP 头);对于环回接口(loopback interfaces)这是真的,但对于多数主机和网络来说不太现实。

  • MTU能支持数据报文(datagram)的最大值(以目前链路层技术来说)。对于任何连接,IPv4允许的最小值为68MTU,推荐值为576(通常推荐作拨号应用的MTU),无论他们是完整接收还是碎片接收。

    对于IPv6MTU的最小值为1280字节,最小碎片缓存大小为1500字节。16字节实在是太小,所以目前链路层一般最小MTU大小为1500

我们不可能知道一个包可能进过的每个连接的MTU。通常发送一个超过接收端MTU大小的数据报文(datagram)会失效。(数据包会被悄悄的抛弃,不会通知发送端数据包没有到达接收端)。

socket.bind(port[, address][, callback])

  • port整数
  • address字符串,可选
  • callback没有参数的函数,可选。绑定时会调用回调。

对于UDP socket,在一个端口和可选地址上监听数据报文(datagram)。如果没有指定地点,系统将会参数监听所有的地址。绑定完毕后,会触发"listening" 事件,并会调用传入的回调函数。指定监听事件和回调函数非常有用。

一个绑定了的数据报文socket会保持node进程运行来接收数据。

如果绑定失败,会产生错误事件。极少数情况(比如绑定一个关闭的socket)。这个方法会抛出一个错误。

以下是UDP服务器监听端口41234的例子:

var dgram = require("dgram");var server = dgram.createSocket("udp4");server.on("error", function (err) {  console.log("server error:
" + err.stack);  server.close();});server.on("message", function (msg, rinfo) {  console.log("server got: " + msg + " from " +    rinfo.address + ":" + rinfo.port);});server.on("listening", function () {  var address = server.address();  console.log("server listening " +      address.address + ":" + address.port);});server.bind(41234);// server listening 0.0.0.0:41234

socket.bind(options[, callback])

  • options{对象} - 必需. 有以下的属性:
    • port{Number} - 必需.
    • address{字符串} - 可选.
    • exclusive{Boolean} - 可选.
  • callback{函数} - 可选.

options的可选参数portaddress,以及可选参数callback,好像在调用socket.bind(port, [address], [callback])

如果exclusivefalse(默认),集群进程将会使用相同的底层句柄,允许连接处理共享的任务。当exclusivetrue时,句柄不会共享,尝试共享端口也会失败。监听exclusive端口的例子如下:

socket.bind({  address: 'localhost',  port: 8000,  exclusive: true});

socket.close()

关闭底层socket并且停止监听数据。

socket.address()

返回一个包含套接字地址信息的对象。对于UDP socket,这个对象会包含addressfamilyport

socket.setBroadcast(flag)

  • flagBoolean

设置或清除SO_BROADCASTsocket选项。设置这个选项后,UDP包可能会发送给一个本地的接口广播地址。

socket.setTTL(ttl)

  • ttl整数

设置IP_TTLsocket选项。TTL表示生存时间(Time to Live),但是在这个上下文中它指的是报文允许通过的IP跃点数。各个转发报文的路由器或者网关都会递减 TTL。如果TTL被路由器递减为0,则它将不会被转发。改变TTL的值通常用于网络探测器或多播。

setTTL()的参数为1到255的跃点数。多数系统默认值为64。

socket.setMulticastTTL(ttl)

  • ttl整数

设置IP_MULTICAST_TTLsocket选项。TTL表示生存时间(Time to Live),但是在这个上下文中它指的是报文允许通过的IP跃点数。各个转发报文的路由器或者网关都会递减TTL。如果TTL被路由器递减为0,则它将不会被转发。改变TTL的值通常用于网络探测器或多播。

setMulticastTTL()的参数为1到255的跃点数。多数系统默认值为1。

socket.setMulticastLoopback(flag)

  • flag Boolean

设置或清空IP_MULTICAST_LOOPsocket选项。设置完这个选项后,当该选项被设置时,组播报文也会被本地接口收到。

socket.addMembership(multicastAddress[, multicastInterface])

  • multicastAddress字符串
  • multicastInterface字符串,可选

告诉内核加入广播组,选项为IP_ADD_MEMBERSHIPsocket

如果没有指定multicastInterface,操作系统会给所有可用的接口添加关系。

socket.dropMembership(multicastAddress[, multicastInterface])

  • multicastAddress字符串
  • multicastInterface字符串,可选

addMembership相反 - 用IP_DROP_MEMBERSHIP选项告诉内核离开广播组 。如果没有指定multicastInterface,操作系统会移除所有可用的接口关系。

socket.unref()

在socket上调用unref允许程序退出,如果这是在事件系统中唯一的活动socket。如果socket已经unref,再次调用unref将会无效。

socket.ref()

unref相反,如果这是唯一的socket,在一个之前被unref了的socket上调用ref将不会让程序退出(缺省行为)。如果一个socket已经被ref,则再次调用ref将会无效。


稳定性: 3 - 稳定

Node.js的URL模块提供了用于分析和解析URL的实用程序。可以调用require('url')来访问它:

const url = require('url');

解析URL对象有以下内容,依赖于他们是否在URL字符串里存在。任何不在URL字符串里的部分,都不会出现在解析对象里。例子如下:

'http://user:pass@host.com:8080/p/a/t/h?query=string#hash'

  • href:准备解析的完整的URL,包含协议和主机(小写)。

    例子:'http://user:pass@host.com:8080/p/a/t/h?query=string#hash'

  • protocol: 请求协议,小写。

    例子:'http:'

  • slashes: 协议要求的斜杠(冒号后)

    例子:true或false

  • host: 完整的URL小写主机部分,包含端口信息。

    例子:'host.com:8080'

  • auth: url中的验证信息。

    例子:'user:pass'

  • hostname: 域名中的小写主机名

    例子:'host.com'

  • port: 主机的端口号

    例子:'8080'

  • pathname: URL中的路径部分,在主机名后,查询字符前,包含第一个斜杠。

    例子:'/p/a/t/h'

  • search: URL中得查询字符串,包含开头的问号

    例子:'?query=string'

  • path: pathnamesearch连在一起

    例子:'/p/a/t/h?query=string'

  • query: 查询字符串中得参数部分,或者使用querystring.parse()解析后返回的对象。

    例子:'query=string'或者{'query':'string'}

  • hash: URL的“#”后面部分(包括 # 符号)

    例子:'#hash'

URL模块提供了以下方法:

url.parse(urlStr[, parseQueryString][, slashesDenoteHost])

输入URL字符串,返回一个对象。

第二个参数为true时,使用querystring来解析查询字符串。如果为truequery属性将会一直赋值为对象,并且search属性将会一直是字符串(可能为空)。默认为false

第三个参数为true,把//foo/bar当做{ host: 'foo', pathname: '/bar' } ,而不是{ pathname: '//foo/bar' }。默认为false

url.format(urlObj)

输入一个解析过的URL对象,返回格式化过的字符串。

格式化的工作流程:

  • href会被忽略
  • protocol无论是否有末尾的 : (冒号),会同样的处理
    • httphttpsftpgopherfile协议会被添加后缀://
    • mailtoxmppaimsftpfoo等协议添加后缀:
  • slashes如果协议需要://,设置为true。
    • 仅需对之前列出的没有斜杠的协议,比如议mongodb://localhost:8000/
  • auth如果出现将会使用.
  • hostname仅在缺少host时使用
  • port仅在缺少host时使用
  • host用来替换hostnameport
  • pathname无论结尾是否有“/”将会同样处理
  • search将会替 query属性
    • 无论前面是否有“/”将会同样处理
  • query (对象;参见querystring) 如果没有search,将会使用
  • hash无论前面是否有#,都会同样处理

url.resolve(from, to)

给一个基础URL,href URL,如同浏览器一样的解析它们可以带上锚点,例如:

url.resolve('/one/two/three', 'four')         // '/one/two/four'url.resolve('http://example.com/', '/one')    // 'http://example.com/one'url.resolve('http://example.com/one', '/two') // 'http://example.com/two'


稳定性: 4 - 锁定

本节介绍Node.js的'util'模块中的函数的使用,通过require('util')访问该模块,如下所示:

const util = require('util');

util 模块原先设计的初衷是用来支持Node.js的内部API的。这里的很多的函数对你的程序来说都非常有用。如果你觉得这些函数不能满足你的要求,那你可以写自己的工具函数。我们不希望'util'模块里添加对于node内部函数无用的扩展。

util.debuglog(section)

  • section{字符串} 被调试的程序节点部分
  • Returns: {Function} 日志函数

用来创建一个有条件的写到stderr的函数(基于NODE_DEBUG环境变量)。如果section出现在环境变量里,返回函数将会和console.error()类似。否则,返回一个空函数。

例如:

javascriptvar debuglog = util.debuglog('foo');var bar = 123;debuglog('hello from foo [%d]', bar);

如果这个程序以NODE_DEBUG=foo的环境运行,将会输出:

FOO 3245: hello from foo [123]

3245是进程ID。如果没有运行在这个环境变量里,将不会打印任何东西。

可以用逗号切割多个NODE_DEBUG环境变量。例如:NODE_DEBUG=fs,net,tls

util.format(format[, ...])

使用第一个参数返回一个格式化的字符串,类似printf

第一个参数是字符串,它包含0或更多的占位符。每个占位符被替换成想要参数转换的值。支持的占位符包括:

  • %s- 字符串.
  • %d- 数字 (整数和浮点数).
  • %j- JSON。如果参数包含循环引用,将会用字符串替换R
  • %%- 单独一个百分号 ('%')。 不会消耗一个参数。

如果占位符没有包含一个相应的参数,占位符不会被替换。

util.format('%s:%s', 'foo'); // 'foo:%s'

如果参数超过占位符,多余的参数将会用util.inspect()转换成字符串,并拼接在一起,用空格隔开。

util.format('%s:%s', 'foo', 'bar', 'baz'); // 'foo:bar baz'

如果第一个参数不是格式化字符串,那么util.format()会返回所有参数拼接成的字符串(空格分割)。每个参数都会用util.inspect()转换成字符串。

util.format(1, 2, 3); // '1 2 3'

util.log(string)

stdout输出并带有时间戳:

require('util').log('Timestamped message.');

util.inspect(object[, options])

返回一个对象的字符串表现形式,在代码调试的时候非常有用。

通过加入一些可选选项,来改变对象的格式化输出形式:

  • showHidden- 如果为true,将会显示对象的不可枚举属性。默认为false

  • depth- 告诉inspect格式化对象时递归多少次。这在格式化大且复杂对象时非常有用。默认为 2。如果想无穷递归的话,传null

  • colors- 如果为true,输出内容将会格式化为有颜色的代码。默认为false, 颜色可以自定义,参见下文。

  • customInspect- 如果为false,那么定义在被检查对象上的inspect(depth, opts) 方法将不会被调用。 默认为true。

检查util对象上所有属性的例子:

var util = require('util');console.log(util.inspect(util, { showHidden: true, depth: null }));

当被调用的时候,参数值可以提供自己的自定义inspect(depth, opts)方法。该方法会接收当前的递归检查深度,以及传入util.inspect()的其他参数。

自定义util.inspect颜色

util.inspect通过util.inspect.stylesutil.inspect.colors对象,自定义全局的输出颜色,

util.inspect.stylesutil.inspect.colors组成风格颜色的一对映射。

高亮风格和他们的默认值:

  • 数字 (黄色)
  • boolean (黄色)
  • 字符串 (绿色)
  • date (洋红)
  • regexp (红色)
  • null (粗体)
  • undefined (斜体)
  • special (青绿色)
  • name (内部用,不是风格)

预定义的颜色为: white斜体blackbluecyan绿色洋红红色黄色以及粗体斜体下划线反选风格。

对象自定义inspect()函数

对象也能自定义inspect(depth)函数, 当使用util.inspect()检查该对象的时候,将会执行对象自定义的检查方法:

var util = require('util');var obj = { name: 'nate' };obj.inspect = function(depth) {  return '{' + this.name + '}';};util.inspect(obj);  // "{nate}"

你可以返回另外一个对象,返回的字符串会根据返回的对象格式化。这和JSON.stringify()的工作流程类似。您还可以完全返回另一个对象,返回的字符串将根据返回的对象格式化。这与JSON.stringify()的工作方式类似:

var obj = { foo: 'this will not show up in the inspect() output' };obj.inspect = function(depth) {  return { bar: 'baz' };};util.inspect(obj);  // "{ bar: 'baz' }"

util.isArray(object)

Array.isArray的内部别名。

如果参数"object"是数组,返回true ,否则返回false

var util = require('util');util.isArray([])  // trueutil.isArray(new Array)  // trueutil.isArray({})  // false

util.isRegExp(object)

如果参数"object"是RegExp返回true ,否则返回false

var util = require('util');util.isRegExp(/some regexp/)  // trueutil.isRegExp(new RegExp('another regexp'))  // trueutil.isRegExp({})  // false

util.isDate(object)

如果参数"object"是Date返回true,否则返回false

var util = require('util');util.isDate(new Date())  // trueutil.isDate(Date())  // false (without 'new' returns a String)util.isDate({})  // false

util.isError(object)

如果参数"object"是Error返回true,否则返回false

var util = require('util');util.isError(new Error())  // trueutil.isError(new TypeError())  // trueutil.isError({ name: 'Error', message: 'an error occurred' })  // false

util.inherits(constructor, superConstructor)

从一个构造函数constructor继承原型方法到另一个。构造函数的原型将被设置为一个新的从超类(superConstructor)创建的对象。

通过constructor.super_属性可以访问superConstructor

var util = require("util");var events = require("events");function MyStream() {    events.EventEmitter.call(this);}util.inherits(MyStream, events.EventEmitter);MyStream.prototype.write = function(data) {    this.emit("data", data);}var stream = new MyStream();console.log(stream instanceof events.EventEmitter); // trueconsole.log(MyStream.super_ === events.EventEmitter); // truestream.on("data", function(data) {    console.log('Received data: "' + data + '"');})stream.write("It works!"); // Received data: "It works!"

util.deprecate(function, string)

标明该方法不要再使用。

exports.puts = exports.deprecate(function() {  for (var i = 0, len = arguments.length; i < len; ++i) {    process.stdout.write(arguments[i] + '
');  }}, 'util.puts: Use console.log instead')

返回一个修改过的函数,默认情况下仅警告一次。如果设置了--no-deprecation该函数不做任何事。如果设置了--throw-deprecation,如果使用了该API应用将会抛出异常。

util.debug(string)

稳定性: 0 - 抛弃: 使用 console.error() 替换。

console.error的前身。

util.error([...])

稳定性: 0 - 抛弃: 使用 console.error() 替换。

console.error的前身。

util.puts([...])

稳定性: 0 - 抛弃:使用 console.log() 替换。

console.log的前身。

util.print([...])

稳定性: 0 - 抛弃: 使用 console.log() 替换。

console.log的前身。

util.pump(readableStream, writableStream[, callback])

稳定性: 0 - 抛弃: Use readableStream.pipe(writableStream)

stream.pipe的前身。


稳定性: 3 - 稳定

本节介绍了Node.js的虚拟机(VM)模块,该模块提供了用于在V8虚拟机上下文中编译和运行代码的API。

可以通过以下方法访问该模块:

var vm = require('vm');

JavaScript 可以立即编译立即执行,也可以编译,保存,之后再运行。

vm.runInThisContext(code[, options])

vm.runInThisContext()对参数code编译,运行并返回结果。运行的代码没有权限访问本地作用域(local scope),但是可以访问全局对象。

使用vm.runInThisContexteval方法运行同样代码的例子:

var localVar = 'initial value';var vmResult = vm.runInThisContext('localVar = "vm";');console.log('vmResult: ', vmResult);console.log('localVar: ', localVar);var evalResult = eval('localVar = "eval";');console.log('evalResult: ', evalResult);console.log('localVar: ', localVar);// vmResult: 'vm', localVar: 'initial value'// evalResult: 'eval', localVar: 'eval'

vm.runInThisContext没有访问本地作用域,所以没有改变localVareval范围了本地作用域,所以改变了localVar

vm.runInThisContext用起来很像间接调用eval,比如(0,eval)('code')。但是,vm.runInThisContext也包含以下选项:

  • filename: 允许更改显示在站追踪(stack traces)的文件名。
  • displayErrors: 是否在stderr上打印错误,抛出异常的代码行高亮显示。会捕获编译时的语法错误,和执行时抛出的错误。默认为true
  • timeout: 中断前代码执行的毫秒数。如果执行终止,将会抛出错误。

vm.createContext([sandbox])

如果参数sandbox不为空,调用vm.runInContextscript.runInContext时可以调用沙箱的上下文。以此方式运行的脚本,sandbox是全局对象,它保留自己的属性同时拥有标准全局对象(global object)拥有的内置对象和函数。

如果参数sandbox对象为空,返回一个可用的新且空的上下文相关的沙盒对象。

这个函数对于创建一个可运行多脚本的沙盒非常有用。比如,在模拟浏览器的时候可以使用该函数创建一个用于表示window全局对象的沙箱,并将所有<script>标签放入沙箱执行。

vm.isContext(sandbox)

沙箱对象是否已经通过调用vm.createContext上下文化。

vm.runInContext(code, contextifiedSandbox[, options])

vm.runInContext编译代码,运行在contextifiedSandbox并返回结果。运行代码不能访问本地域。contextifiedSandbox对象必须通过 vm.createContext上下文化;code 会通过全局变量使用它。

vm.runInContextvm.runInThisContext参数相同。

在同一个上下文中编译并执行不同的脚本,例子:

var util = require('util');var vm = require('vm');var sandbox = { globalVar: 1 };vm.createContext(sandbox);for (var i = 0; i < 10; ++i) {    vm.runInContext('globalVar *= 2;', sandbox);}console.log(util.inspect(sandbox));// { globalVar: 1024 }

注意,执行不被信任的代码是需要技巧且要非常的小心。vm.runInContext非常有用,不过想要安全些,最好还是在独立的进程里运行不被信任的代码。

vm.runInNewContext(code[, sandbox][, options])

vm.runInNewContext编译代码, 如果提供了sandbox ,则将sandbox上下文化,否则创建一个新的上下文化过的沙盒,将沙盒作为全局变量运行代码并返回结果。

vm.runInNewContextvm.runInThisContext参数相同。

编译并执行代码,增加全局变量值,并设置一个新的。这些全局变量包含在一个新的沙盒里。

var util = require('util');var vm = require('vm'),var sandbox = {  animal: 'cat',  count: 2};vm.runInNewContext('count += 1; name = "kitty"', sandbox);console.log(util.inspect(sandbox));// { animal: 'cat', count: 3, name: 'kitty' }

注意,执行不被信任的代码是需要技巧且要非常的小心。vm.runInNewContext非常有用,不过想要安全些,最好还是在独立的进程里运行不被信任的代码。

vm.runInDebugContext(code)

vm.runInDebugContext在V8的调试上下文中编译并执行。最主要的应用场景是获得V8调试对象访问权限。

var Debug = vm.runInDebugContext('Debug');Debug.scripts().forEach(function(script) { console.log(script.name); });

注意,调试上下文和对象内部绑定到V8的调试实现里,并可能在没有警告时改变(或移除)。

可以通过--expose_debug_as=开关暴露调试对象。

Class: Script

包含预编译脚本的类,并在指定的沙盒里执行。

new vm.Script(code, options)

创建一个新的脚本编译代码,但是不运行。使用被创建的vm.Script来表示编译完的代码。这个代码可以使用以下的方法调用多次。返回的脚本没有绑定到任何全局变量。在运行前绑定,执行后释放。

创建脚本的选项有:

  • filename: 允许更改显示在站追踪(stack traces)的文件名。
  • displayErrors: 是否在stderr上打印错误,抛出异常的代码行高亮显示。只会捕获编译时的语法错误,执行时抛出的错误由脚本的方法的选项来控制。默认为 true

script.runInThisContext([options])

vm.runInThisContext类似,只是作为Script脚本对象的预编译方法。script.runInThisContext执行编译过的脚本并返回结果。被运行的代码没有本地作用域访问权限,但是拥有权限访问全局对象。

以下例子,使用script.runInThisContext编译代码一次,并运行多次:

var vm = require('vm');global.globalVar = 0;var script = new vm.Script('globalVar += 1', { filename: 'myfile.vm' });for (var i = 0; i < 1000; ++i) {  script.runInThisContext();}console.log(globalVar);// 1000

所运行的代码选项:

  • displayErrors: 是否在stderr上打印错误,抛出异常的代码行高亮显示。仅适用于执行时抛出的错误。不能创建一个语法错误的Script实例,因为构造函数会抛出。
  • timeout: 中断前代码执行的毫秒数。如果执行终止,将会抛出错误。

script.runInContext(contextifiedSandbox[, options])

vm.runInContext类似,只是作为预编译的Script对象方法。script.runInContext运行脚本(在contextifiedSandbox中编译)并返回结果。运行的代码没有权限访问本地域。

script.runInContext的选项和script.runInThisContext类似。

例子:编译一段代码,并执行多次,这段代码实现了一个全局变量的自增,并创建一个新的全局变量。这些全局变量保存在沙盒里。

var util = require('util');var vm = require('vm');var sandbox = {  animal: 'cat',  count: 2};var script = new vm.Script('count += 1; name = "kitty"');for (var i = 0; i < 10; ++i) {  script.runInContext(sandbox);}console.log(util.inspect(sandbox));// { animal: 'cat', count: 12, name: 'kitty' }

注意,执行不被信任的代码是需要技巧且要非常的小心。script.runInContext非常有用,不过想要安全些,最好还是在独立的进程里运行不被信任的代码。

script.runInNewContext([sandbox][, options])

vm.runInNewContext类似,只是作为预编译的Script对象方法。若提供sandbox则script.runInNewContext将sandbox上下文化,若未提供,则创建一个新的上下文化的沙箱。

script.runInNewContextscript.runInThisContext的参数类似。

例子:编译代码(设置了一个全局变量)并在不同的上下文里执行多次。这些全局变量会被保存在沙箱中。

var util = require('util');var vm = require('vm');var sandboxes = [{}, {}, {}];var script = new vm.Script('globalVar = "set"');sandboxes.forEach(function (sandbox) {  script.runInNewContext(sandbox);});console.log(util.inspect(sandboxes));// [{ globalVar: 'set' }, { globalVar: 'set' }, { globalVar: 'set' }]

注意,执行不被信任的代码是需要技巧且要非常的小心。script.runInNewContext非常有用,不过想要安全些,最好还是在独立的进程里运行不被信任的代码。


稳定性: 3 - 文档

本节介绍Node.js中ZLIB模块的使用,你可以通过以下方式访问这个模块:

var zlib = require('zlib');

这个模块提供了对Gzip/Gunzip, Deflate/Inflate, 和 DeflateRaw/InflateRaw类的绑定。每个类都有相同的参数和可读/写的流。

例子

压缩/解压缩一个文件,可以通过倒流(piping)一个fs.ReadStream到zlib流里来,再到一个fs.fs.WriteStream:

var gzip = zlib.createGzip();var fs = require('fs');var inp = fs.createReadStream('input.txt');var out = fs.createWriteStream('input.txt.gz');inp.pipe(gzip).pipe(out);

一步压缩/解压缩数据可以通过一个简便方法来实现。

var input = '.................................';zlib.deflate(input, function(err, buffer) {  if (!err) {    console.log(buffer.toString('base64'));  }});var buffer = new Buffer('eJzT0yMAAGTvBe8=', 'base64');zlib.unzip(buffer, function(err, buffer) {  if (!err) {    console.log(buffer.toString());  }});

要在一个HTTP客户端或服务器中使用这个模块,可以在请求时使用accept-encoding,响应时使用content-encoding头。

注意: 这些例子只是简单展示了基本概念。Zlib编码可能消耗非常大,并且结果可能要被缓存。更多使用 zlib 相关的速度/内存/压缩的权衡选择细节参见后面的Memory Usage Tuning

// client request examplevar zlib = require('zlib');var http = require('http');var fs = require('fs');var request = http.get({ host: 'izs.me',                         path: '/',                         port: 80,                         headers: { 'accept-encoding': 'gzip,deflate' } });request.on('response', function(response) {  var output = fs.createWriteStream('izs.me_index.html');  switch (response.headers['content-encoding']) {    // or, just use zlib.createUnzip() to handle both cases    case 'gzip':      response.pipe(zlib.createGunzip()).pipe(output);      break;    case 'deflate':      response.pipe(zlib.createInflate()).pipe(output);      break;    默认:      response.pipe(output);      break;  }});// server example// Running a gzip operation on every request is quite expensive.// It would be much more efficient to cache the compressed buffer.var zlib = require('zlib');var http = require('http');var fs = require('fs');http.createServer(function(request, response) {  var raw = fs.createReadStream('index.html');  var acceptEncoding = request.headers['accept-encoding'];  if (!acceptEncoding) {    acceptEncoding = '';  }  // Note: this is not a conformant accept-encoding parser.  // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3  if (acceptEncoding.match(/deflate/)) {    response.writeHead(200, { 'content-encoding': 'deflate' });    raw.pipe(zlib.createDeflate()).pipe(response);  } else if (acceptEncoding.match(/gzip/)) {    response.writeHead(200, { 'content-encoding': 'gzip' });    raw.pipe(zlib.createGzip()).pipe(response);  } else {    response.writeHead(200, {});    raw.pipe(response);  }}).listen(1337);

zlib.createGzip([options])

根据参数options返回一个新的Gzip对象。

zlib.createGunzip([options])

根据参数options返回一个新的Gunzip对象。

zlib.createDeflate([options])

根据参数options返回一个新的Deflate对象。

zlib.createInflate([options])

根据参数options返回一个新的Inflate对象。

zlib.createDeflateRaw([options])

根据参数options返回一个新的DeflateRaw对象。

zlib.createInflateRaw([options])

根据参数options返回一个新的InflateRaw对象。

zlib.createUnzip([options])

根据参数options返回一个新的Unzip对象。

Class: zlib.Zlib

这个类未被zlib模块导出。之所以写在这,是因为这是压缩/解压缩类的基类。

zlib.flush([kind], callback)

参数kind默认为zlib.Z_FULL_FLUSH

刷入缓冲数据。不要轻易调用这个方法,过早的刷会对压缩算法产生负面影响。

zlib.params(level, strategy, callback)

动态更新压缩基本和压缩策略。仅对deflate算法有效。

zlib.reset()

重置压缩/解压缩为默认值。仅适用于inflate和deflate算法。

Class: zlib.Gzip

使用gzip压缩数据。

Class: zlib.Gunzip

使用gzip解压缩数据。

Class: zlib.Deflate

使用deflate压缩数据。

Class: zlib.Inflate

解压缩deflate流。

Class: zlib.DeflateRaw

使用deflate压缩数据,不需要拼接zlib头。

Class: zlib.InflateRaw

解压缩一个原始deflate流。

Class: zlib.Unzip

通过自动检测头解压缩一个Gzip-或Deflate-compressed流。

简便方法

所有的这些方法第一个参数为字符串或缓存,第二个可选参数可以供zlib类使用,回调函数为callback(error, result)

每个方法都有一个*Sync伴随方法,它接收相同参数,不过没有回调。

zlib.deflate(buf[, options], callback)

zlib.deflateSync(buf[, options])

使用Deflate压缩一个字符串。

zlib.deflateRaw(buf[, options], callback)

zlib.deflateRawSync(buf[, options])

使用DeflateRaw压缩一个字符串。

zlib.gzip(buf[, options], callback)

zlib.gzipSync(buf[, options])

使用Gzip压缩一个字符串。

zlib.gunzip(buf[, options], callback)

zlib.gunzipSync(buf[, options])

使用Gunzip解压缩一个原始的Buffer。

zlib.inflate(buf[, options], callback)

zlib.inflateSync(buf[, options])

使用Inflate解压缩一个原始的Buffer。

zlib.inflateRaw(buf[, options], callback)

zlib.inflateRawSync(buf[, options])

使用InflateRaw解压缩一个原始的Buffer。

zlib.unzip(buf[, options], callback)

zlib.unzipSync(buf[, options])

使用Unzip解压缩一个原始的Buffer。

Options

每个类都有一个选项对象。所有选项都是可选的。

注意:某些选项仅在压缩时有用,解压缩时会被忽略。

  • flush (默认:zlib.Z_NO_FLUSH)
  • chunkSize (默认:16*1024)
  • windowBits
  • level (仅压缩有效)
  • memLevel (仅压缩有效)
  • strategy (仅压缩有效)
  • dictionary (仅 deflate/inflate 有效, 默认为空字典)

参见deflateInit2inflateInit2的描述,它们位于http://zlib.net/manual.html#Advanced

使用内存调优

来自zlib/zconf.h,修改为node's的用法:

deflate的内存需求(单位:字节):

(1 << (windowBits+2)) +  (1 << (memLevel+9))

windowBits=15的128K加memLevel = 8的128K (缺省值),加其他对象的若干KB。

例如,如果你想减少默认的内存需求(从256K减为128k),设置选项:

{ windowBits: 14, memLevel: 7 }

当然这通常会降低压缩等级。

inflate的内存需求(单位:字节):

1 << windowBits

windowBits=15(默认值)32K 加其他对象的若干KB。

这是除了内部输出缓冲外chunkSize的大小,默认为16K。

影响zlib的压缩速度最大因素为level压缩级别。level越大,压缩率越高,速度越慢,level越小,压缩率越小,速度会更快。

通常来说,使用更多的内存选项,意味着node必须减少对zlib掉哟过,因为可以在一个write操作里可以处理更多的数据。所以,这是另一个影响速度和内存使用率的因素,

常量

所有常量定义在zlib.h ,也定义在require('zlib')

通常的操作,基本用不到这些常量。写到文档里是想你不会对他们的存在感到惊讶。这个章节基本都来自zlibdocumentation。更多细节参见http://zlib.net/manual.html#Constants

允许flush的值:

  • zlib.Z_NO_FLUSH
  • zlib.Z_PARTIAL_FLUSH
  • zlib.Z_SYNC_FLUSH
  • zlib.Z_FULL_FLUSH
  • zlib.Z_FINISH
  • zlib.Z_BLOCK
  • zlib.Z_TREES

压缩/解压缩函数的返回值。负数代表错误,正数代表特殊但正常的事件:

  • zlib.Z_OK
  • zlib.Z_STREAM_END
  • zlib.Z_NEED_DICT
  • zlib.Z_ERRNO
  • zlib.Z_STREAM_ERROR
  • zlib.Z_DATA_ERROR
  • zlib.Z_MEM_ERROR
  • zlib.Z_BUF_ERROR
  • zlib.Z_VERSION_ERROR

压缩级别:

  • zlib.Z_NO_COMPRESSION
  • zlib.Z_BEST_SPEED
  • zlib.Z_BEST_COMPRESSION
  • zlib.Z_DEFAULT_COMPRESSION

压缩策略:

  • zlib.Z_FILTERED
  • zlib.Z_HUFFMAN_ONLY
  • zlib.Z_RLE
  • zlib.Z_FIXED
  • zlib.Z_DEFAULT_STRATEGY

data_type字段的可能值:

  • zlib.Z_BINARY
  • zlib.Z_TEXT
  • zlib.Z_ASCII
  • zlib.Z_UNKNOWN

deflate的压缩方法:

  • zlib.Z_DEFLATED

初始化zalloc, zfree, opaque:

  • zlib.Z_NULL
None

Node.js v8.3.0已发布,在该版本中,已将V8引擎升级到6.0版本,性能有了大幅度的改进。有关性能差异的更多详细信息,点击查看详情

除此之外,还带来了循环遍历对象,删除对象键,函数绑定和对象创建等实用的功能。下面W3C小编就给大家带来此次更新的一些主要内容。

  • DNS
  • 现在支持独立的 DNS 解析程序实例, 并支持取消相应的请求。#14518
  • N-API
  • 错误处理的多个N-API函数已更改为支持分配错误代码。#13988
  • REPL
  • 对require()的自动完成支持已经改进。#14409
  • Utilities
  • WHATWG编码标准 (TextDecoder 和 TextEncoder) 作为一个实验性的特点被实现。#13644
  • 增加了新collaborators
  • XadillaX – Khaidi Chugabrielschulhof – Gabriel Schulhof

相关下载地址:

Node.js是一个基于 Chrome V8 引擎的JavaScript运行时。Node.js使用高效、轻量级的事件驱动、非阻塞I/O模型。

稳定性: 3 - 稳定

Node.js路径(path)模块包含一系列用于处理和转换文件路径的工具集。基本所有的反复都仅对字符串转换。文件系统不会检查路径是否有效。

你可以通过require('path')来访问这个模块:

const path = require('path');

Node.js路径模块包含下文中介绍的方法:

path.normalize(p)

用于规范化路径,注意'..''.'

发现多个斜杠时,会替换成一个斜杠。当路径末尾包含一个斜杠时,保留。

Windows系统使用反斜杠。

例如:

path.normalize('/foo/bar//baz/asdf/quux/..')// returns'/foo/bar/baz/asdf'

path.join([path1][, path2][, ...])

用于连接所有的参数,并规范化输出路径。

参数必须是字符串。在v0.8版本,非字符参数会被忽略。v0.10之后的版本后抛出异常。

例如:

path.join('/foo', 'bar', 'baz/asdf', 'quux', '..')// returns'/foo/bar/baz/asdf'path.join('foo', {}, 'bar')// throws exceptionTypeError: Arguments to path.join must be strings

path.resolve([from ...], to)

能够将to参数解析为绝对路径。

如果参数to不是一个相对于参数from的绝对路径,to会添加到from右侧,直到找到一个绝对路径为止。如果使用所有from参数后,还是没有找到绝对路径,将会使用当前工作目录。返回的路径已经规范化过,并且去掉了尾部的斜杠(除非是根目录)。非字符串的参数会被忽略。

另一种思路就是在shell里执行一系列的cd命令。

path.resolve('foo/bar', '/tmp/file/', '..', 'a/../subfile')

类似于:

cd foo/barcd /tmp/file/cd ..cd a/../subfilepwd

不同点是,不同的路径不需要存在的,也可能是文件。

例如:

path.resolve('/foo/bar', './baz')// returns'/foo/bar/baz'path.resolve('/foo/bar', '/tmp/file/')// returns'/tmp/file'path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif')// if currently in /home/myself/node, it returns'/home/myself/node/wwwroot/static_files/gif/image.gif'

path.isAbsolute(path)

判断参数path是否是绝对路径。一个绝对路径解析后都会指向相同的位置,无论当前的工作目录在哪里。

Posix例子:

path.isAbsolute('/foo/bar') // truepath.isAbsolute('/baz/..')  // truepath.isAbsolute('qux/')     // falsepath.isAbsolute('.')        // false

Windows例子:

path.isAbsolute('//server')  // truepath.isAbsolute('C:/foo/..') // truepath.isAbsolute('baraz')   // falsepath.isAbsolute('.')         // false

path.relative(from, to)

解决从fromto的相对路径。

有时我们会有2个绝对路径,需要从中找到相对目录。这是path.resolve的逆实现:

path.resolve(from, path.relative(from, to)) == path.resolve(to)

例如:

path.relative('C:orandea	estaaa', 'C:orandeaimplbb')// returns'....implbb'path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb')// returns'../../impl/bbb'

path.dirname(p)

返回路径p所在的目录。和Unixdirname命令类似。

例如:

path.dirname('/foo/bar/baz/asdf/quux')// returns'/foo/bar/baz/asdf'

path.basename(p[, ext])

返回路径的最后一个部分。和Unixbasename命令类似。

例如:

path.basename('/foo/bar/baz/asdf/quux.html')// returns'quux.html'path.basename('/foo/bar/baz/asdf/quux.html', '.html')// returns'quux'

path.extname(p)

返回路径p的扩展名,从最后一个'.'到字符串的末尾。如果最后一个部分没有'.' ,或者路径是以'.'开头,则返回空字符串。例如:

path.extname('index.html')// returns'.html'path.extname('index.coffee.md')// returns'.md'path.extname('index.')// returns'.'path.extname('index')// returns''

path.sep

特定平台的文件分隔符,'''/'

*nix上的例子:

'foo/bar/baz'.split(path.sep)// returns['foo', 'bar', 'baz']

Windows的例子:

'fooaraz'.split(path.sep)// returns['foo', 'bar', 'baz']

path.delimiter

特定平台的分隔符,;或者':'

*nix上的例子:

console.log(process.env.PATH)// '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin'process.env.PATH.split(path.delimiter)// returns['/usr/bin', '/bin', '/usr/sbin', '/sbin', '/usr/local/bin']

Windows例子:

console.log(process.env.PATH)// 'C:Windowssystem32;C:Windows;C:Program Files
odejs'process.env.PATH.split(path.delimiter)// returns['C:Windowssystem32', 'C:Windows', 'C:Program Files
odejs']

path.parse(pathString)

返回路径字符串的对象。

*nix上的例子:

path.parse('/home/user/dir/file.txt')// returns{    root : "/",    dir : "/home/user/dir",    base : "file.txt",    ext : ".txt",    name : "file"}

Windows例子:

path.parse('C:pathdirindex.html')// returns{    root : "C:",    dir : "C:pathdir",    base : "index.html",    ext : ".html",    name : "index"}

path.format(pathObject)

从对象中返回路径字符串,和path.parse相反:

path.format({    root : "/",    dir : "/home/user/dir",    base : "file.txt",    ext : ".txt",    name : "file"})// returns'/home/user/dir/file.txt'

path.posix

提供上述path路径访问,不过总是以posix兼容的方式交互。

path.win32

提供上述path路径访问,不过总是以win32兼容的方式交互。


稳定性: 3 - 稳定

Node.js路径(path)模块包含一系列用于处理和转换文件路径的工具集。基本所有的反复都仅对字符串转换。文件系统不会检查路径是否有效。

你可以通过require('path')来访问这个模块:

const path = require('path');

Node.js路径模块包含下文中介绍的方法:

path.normalize(p)

用于规范化路径,注意'..''.'

发现多个斜杠时,会替换成一个斜杠。当路径末尾包含一个斜杠时,保留。

Windows系统使用反斜杠。

例如:

path.normalize('/foo/bar//baz/asdf/quux/..')// returns'/foo/bar/baz/asdf'

path.join([path1][, path2][, ...])

用于连接所有的参数,并规范化输出路径。

参数必须是字符串。在v0.8版本,非字符参数会被忽略。v0.10之后的版本后抛出异常。

例如:

path.join('/foo', 'bar', 'baz/asdf', 'quux', '..')// returns'/foo/bar/baz/asdf'path.join('foo', {}, 'bar')// throws exceptionTypeError: Arguments to path.join must be strings

path.resolve([from ...], to)

能够将to参数解析为绝对路径。

如果参数to不是一个相对于参数from的绝对路径,to会添加到from右侧,直到找到一个绝对路径为止。如果使用所有from参数后,还是没有找到绝对路径,将会使用当前工作目录。返回的路径已经规范化过,并且去掉了尾部的斜杠(除非是根目录)。非字符串的参数会被忽略。

另一种思路就是在shell里执行一系列的cd命令。

path.resolve('foo/bar', '/tmp/file/', '..', 'a/../subfile')

类似于:

cd foo/barcd /tmp/file/cd ..cd a/../subfilepwd

不同点是,不同的路径不需要存在的,也可能是文件。

例如:

path.resolve('/foo/bar', './baz')// returns'/foo/bar/baz'path.resolve('/foo/bar', '/tmp/file/')// returns'/tmp/file'path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif')// if currently in /home/myself/node, it returns'/home/myself/node/wwwroot/static_files/gif/image.gif'

path.isAbsolute(path)

判断参数path是否是绝对路径。一个绝对路径解析后都会指向相同的位置,无论当前的工作目录在哪里。

Posix例子:

path.isAbsolute('/foo/bar') // truepath.isAbsolute('/baz/..')  // truepath.isAbsolute('qux/')     // falsepath.isAbsolute('.')        // false

Windows例子:

path.isAbsolute('//server')  // truepath.isAbsolute('C:/foo/..') // truepath.isAbsolute('baraz')   // falsepath.isAbsolute('.')         // false

path.relative(from, to)

解决从fromto的相对路径。

有时我们会有2个绝对路径,需要从中找到相对目录。这是path.resolve的逆实现:

path.resolve(from, path.relative(from, to)) == path.resolve(to)

例如:

path.relative('C:orandea	estaaa', 'C:orandeaimplbb')// returns'....implbb'path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb')// returns'../../impl/bbb'

path.dirname(p)

返回路径p所在的目录。和Unixdirname命令类似。

例如:

path.dirname('/foo/bar/baz/asdf/quux')// returns'/foo/bar/baz/asdf'

path.basename(p[, ext])

返回路径的最后一个部分。和Unixbasename命令类似。

例如:

path.basename('/foo/bar/baz/asdf/quux.html')// returns'quux.html'path.basename('/foo/bar/baz/asdf/quux.html', '.html')// returns'quux'

path.extname(p)

返回路径p的扩展名,从最后一个'.'到字符串的末尾。如果最后一个部分没有'.' ,或者路径是以'.'开头,则返回空字符串。例如:

path.extname('index.html')// returns'.html'path.extname('index.coffee.md')// returns'.md'path.extname('index.')// returns'.'path.extname('index')// returns''

path.sep

特定平台的文件分隔符,'''/'

*nix上的例子:

'foo/bar/baz'.split(path.sep)// returns['foo', 'bar', 'baz']

Windows的例子:

'fooaraz'.split(path.sep)// returns['foo', 'bar', 'baz']

path.delimiter

特定平台的分隔符,;或者':'

*nix上的例子:

console.log(process.env.PATH)// '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin'process.env.PATH.split(path.delimiter)// returns['/usr/bin', '/bin', '/usr/sbin', '/sbin', '/usr/local/bin']

Windows例子:

console.log(process.env.PATH)// 'C:Windowssystem32;C:Windows;C:Program Files
odejs'process.env.PATH.split(path.delimiter)// returns['C:Windowssystem32', 'C:Windows', 'C:Program Files
odejs']

path.parse(pathString)

返回路径字符串的对象。

*nix上的例子:

path.parse('/home/user/dir/file.txt')// returns{    root : "/",    dir : "/home/user/dir",    base : "file.txt",    ext : ".txt",    name : "file"}

Windows例子:

path.parse('C:pathdirindex.html')// returns{    root : "C:",    dir : "C:pathdir",    base : "index.html",    ext : ".html",    name : "index"}

path.format(pathObject)

从对象中返回路径字符串,和path.parse相反:

path.format({    root : "/",    dir : "/home/user/dir",    base : "file.txt",    ext : ".txt",    name : "file"})// returns'/home/user/dir/file.txt'

path.posix

提供上述path路径访问,不过总是以posix兼容的方式交互。

path.win32

提供上述path路径访问,不过总是以win32兼容的方式交互。