NodeJs基础。react native中哪向服务器上污染网络图片。

 



参考连接:http://nqdeng.github.io/7-days-nodejs/\#1.1



 

NodeJS基础

什么是NodeJS

JS是脚本语言,脚本语言都用一个解析器才会运作。对于刻画在HTML页面里的JS,浏览器充当了解析器的角色。而对要独自运转的JS,NodeJS就是一个解析器。

诸一样栽解析器都是一个运行条件,不但允许JS定义各种数码结构,进行各种计算,还允许JS使用运行环境提供的放权对象与艺术做一些政工。例如运行于浏览器中的JS的用处是操作DOM,浏览器就提供了document等等的搁对象。而运作于NodeJS中的JS的用途是操作磁盘文件要搭建HTTP服务器,NodeJS就相应提供了fshttp相当内置对象。

发出何用处

尽管在同样听说可以一直运行JS文件就觉着怪死的同桌,但大多数校友在接触新物常常首先关心的凡产生什么用处,以及会带动什么价值。

NodeJS的撰稿人说,他创造NodeJS的目的是为着落实强性能Web服务器,他首先强调的是事件机制及异步IO模型的优越性,而非是JS。但是他需选择同一栽编程语言实现他的想法,这种编程语言不能够打带IO功能,并且用会美支持事件机制。JS没有于带IO功能,天生就是用于拍卖浏览器被的DOM事件,并且具有相同不行群程序员,因此尽管成了天赋之选项。

假如他所愿意,NodeJS在服务端活跃起来,出现了巨因NodeJS的Web服务。而一方面,NodeJS让前方端众而得神器,终于得以为好之能力覆盖范围跳出浏览器窗口,更大批底前端工具要恒河沙数。

所以,对于前端而言,虽然非是人们都设拿NodeJS写一个服务器程序,但略不过及以命令交互模式调试JS代码片段,复杂而到编写工具提升工作效率。

NodeJS生态圈正欣欣向荣。

什么设置

安装程序

NodeJS提供了有的安装程序,都足以于nodejs.org此处下载并设置。

Windows系统下,选择与体系版本匹配的.msi后缀的安装文件。Mac OS
X系统下,选择.pkg后缀的安装文件。

编译安装

Linux系统下并未成的安装程序可用,虽然部分批发本可采用apt-get等等的方法安装,但无肯定能够安装到最新版。因此Linux系统下一般用以下方法编译方式安装NodeJS。

  1. 管教系统下g++版本在4.6以上,python版本在2.6上述。

  2. 从nodejs.org下载tar.gz后缀的NodeJS最新版源代码包并解压到某个位置。

  3. 上解压到之目录,使用以下命令编译和安装。

     $ ./configure
     $ make
     $ sudo make install
    

争运作

开辟终端,键入node跻身命令交互模式,可以输入一修代码语句后立刻实施并显示结果,例如:

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

如若一旦运行一百般段代码的话,可以事先勾勒一个JS文件再次运行。例如有以下hello.js

function hello() {
    console.log('Hello World!');
}
hello();

描绘好后每当终端下键入node hello.js运行,结果如下:

$ node hello.js
Hello World!

权力问题

当Linux系统下,使用NodeJS监听80要443端口提供HTTP(S)服务经常欲root权限,有三三两两种方式可完成。

同一种艺术是行使sudo命令运行NodeJS。例如通过以下命令运行的server.js蒙产生权力行使80及443端口。一般推荐这种方法,可以确保单独为来需要之JS脚本提供root权限。

$ sudo node server.js

其它一样栽方法是动chmod +s指令于NodeJS总是以root权限运作,具体做法如下。因为这种方法于其它JS脚本都生矣root权限,不顶安全,因此于需要分外考虑安全的系下非推荐下。

$ sudo chown root /usr/local/bin/node
$ sudo chmod +s /usr/local/bin/node

模块

编辑稍大一点底顺序时相似还见面将代码模块化。在NodeJS中,一般将代码合理拆分到不同的JS文件中,每一个文书就是一个模块,而文件路径就是是模块名。

于编写每个模块时,都发生requireexportsmodule老三只优先定义好的变量可供使用。

require

require函数用于在此时此刻模块中加载与以别的模块,传入一个模块名,返回一个模块导出对象。模块名可使用相对路径(以./起),或者是绝对路径(以/C:等等的盘符开头)。另外,模块名遭之.js扩张名好简简单单。以下是一个例证。

var foo1 = require('./foo');
var foo2 = require('./foo.js');
var foo3 = require('/home/user/foo');
var foo4 = require('/home/user/foo.js');

// foo1至foo4中保存的是同一个模块的导出对象。

此外,可以运用以下办法加载与使用一个JSON文件。

var data = require('./data.json');

exports

exports对象是当前模块的导出对象,用于导出模块公有方法及性能。别的模块通过require函数使用时模块时获得的就是是时下模块的exports对象。以下例子中导出了一个国有方法。

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

module

通过module目标好拜到当下模块的一部分连锁信息,但极致多之用途是替换当前模块的导出对象。例如模块导出对象默认是一个习以为常对象,如果想改变成为一个函数的语句,可以采取以下方法。

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

如上代码中,模块默认导出对象为调换为一个函数。

模块初始化

一个模块中的JS代码仅于模块第一涂鸦为运时实行同样不成,并于履行过程中初始化模块的导出对象。之后,缓存起来的导出对象被再利用。

主模块

透过命令行参数传递给NodeJS以启动程序的模块于誉为主模块。主模块当调度组成总体程序的外模块形成工作。例如通过以下命令启动程序时,main.js尽管是主模块。

$ node main.js

整示例

如有以下目录。

- /home/user/hello/
    - util/
        counter.js
    main.js

其中counter.js情节如下:

var i = 0;

function count() {
return ++i;
}

exports.count = count;

欠模块内部定义了一个个体变量i,并在exports对象导出了一个国有方法count

主模块main.js情节如下:

var counter1 = require('./util/counter');
var    counter2 = require('./util/counter');

console.log(counter1.count());
console.log(counter2.count());
console.log(counter2.count());

运转该次的结果如下:

$ node main.js
1
2
3

得视,counter.js并不曾坐被require了片不好如初始化两不行。

仲进制模块

尽管如此一般我们使用JS编写模块,但NodeJS也支持使用C/C++编写二进制模块。编译好之二进制模块除了文件扩展名是.node外,和JS模块的运用办法同样。虽然二进制模块能采取操作系统提供的有所功能,拥有无限的潜能,但对前端同学而言编写过于艰苦,并且难以逾越平台采用,因此无以本教程的覆盖范围内。

小结

本章介绍了有关NodeJS的基本概念和运用办法,总结起来有以下知识点:

  • NodeJS是一个JS脚本解析器,任何操作系统下安装NodeJS本质上举行的业务都是管NodeJS执行顺序复制到一个目,然后保证是目录在系统PATH环境变量下,以便终端下足动用node命令。

  • 终端下直接输入node命令可进入命令交互模式,很合乎用来测试一些JS代码片段,比如正则表达式。

  • NodeJS使用CMD模块系统,主模块当次入口点,所有模块于履过程遭到单单初始化一不善。

  • 除非JS模块不能够满足要求,否则不要擅自用二进制模块,否则你的用户会让苦连天。

代码的社同布局

发生经验的C程序员在编辑一个初程序时首先由make文件写于。同样的,使用NodeJS编写程序前,为了闹只地道的上马,首先需要预备好代码的目录结构与配置方式,就不啻修房子如果先增脚手架。本章将介绍和的有关的各种文化。

模块路径解析规则

俺们已经亮,require函数支持斜杠(/)或盘符(C:)开头的绝对路径,也支持./启的相对路径。但随即半种植途径在模块之间确立了强耦合关系,一旦有模块文件的寄放位置要改变,使用该模块的其他模块的代码也得就调整,变得牵一发动全身。因此,require函数支持第三栽样式之路,写法类似于foo/bar,并逐一以以下规则解析路径,直到找到模块位置。

  1. 放到模块

    倘传递让require函数的是NodeJS内置模块名称,不开路径解析,直接归内部模块的导出对象,例如require('fs')

  2. node_modules目录

    NodeJS定义了一个特之node_modules目录用于存放模块。例如有模块的绝对路径是/home/user/hello.js,在该模块中应用require('foo/bar')艺术加载模块时,则NodeJS依次尝试采取以下途径。

     /home/user/node_modules/foo/bar
     /home/node_modules/foo/bar
     /node_modules/foo/bar
    
  3. NODE_PATH环境变量

    跟PATH环境变量类似,NodeJS允许通过NODE_PATH环境变量来指定额外的模块搜索路径。NODE_PATH环境变量中隐含一交多只目录路径,路径之间以Linux下用:分隔,在Windows下使用;隔。例如定义了以下NODE_PATH环境变量:

     NODE_PATH=/home/user/lib:/home/lib
    

    当使用require('foo/bar')的法加载模块时,则NodeJS依次尝试以下途径。

     /home/user/lib/foo/bar
     /home/lib/foo/bar
    

包(package)

我们既清楚了JS模块的为主单位凡单科JS文件,但复杂些的模块往往由于多只子模块组成。为了便于管理和采取,我们得以把由多独子模块组成的坏模块称做,并拿所有子模块放在和一个目里。

以整合一个确保的所有子模块中,需要来一个进口模块,入口模块的导出对象为当保证之导出对象。例如有以下目录结构。

- /home/user/lib/
    - cat/
        head.js
        body.js
        main.js

其中cat目录定义了一个管教,其中蕴蓄了3独子模块。main.js用作入口模块,其情如下:

var head = require('./head');
var body = require('./body');

exports.create = function (name) {
return {
name: name,
head: head.create(),
body: body.create()
};
};

当外模块里以包之时光,需要加载包之进口模块。接着上例,使用require('/home/user/lib/cat/main')会及目的,但是进口模块名称出现在路径里看上去不是只好主意。因此我们得举行点额外的干活,让包下起来还如是单个模块。

index.js

当模块的文件称是index.js,加载模块时方可行使模块所在目录的路子代替模块文件路径,因此就上例,以下简单漫长语句等价。

var cat = require('/home/user/lib/cat');
var cat = require('/home/user/lib/cat/index');

这般处理后,就单待将保证目录路径传递让require函数,感觉上总体目录为作为单个模块使用,更发生整体感。

package.json

万一想由定义入口模块的文本称以及存放位置,就得以包目录下富含一个package.json文件,并于中间指定入口模块的路。上例被之cat模块可重构如下。

- /home/user/lib/
    - cat/
        + doc/
        - lib/
            head.js
            body.js
            main.js
        + tests/
        package.json

其中package.json情如下。

{
    "name": "cat",
    "main": "./lib/main.js"
}

如此一来,就一律可采取require('/home/user/lib/cat')的主意加载模块。NodeJS会根据包目录下的package.json找到入口模块所在位置。

命行程序

行使NodeJS编写的事物,要么是一个保证,要么是一个命行程序,而前者最终为会见用来开发后者。因此我们以安排代码时要有技艺,让用户认为好是在使一个下令行程序。

比如我们为此NodeJS写了单程序,可以将命执行参数原样打印出。该次非常粗略,在主模块内实现了所有功能。并且写好后,我们把欠次部署在/home/user/bin/node-echo.js这位置。为了在旁目录下都能够运行该次,我们用采用以下终端命令。

$ node /home/user/bin/node-echo.js Hello World
Hello World

这种以方式看起有些像是一个命令行程序,下边的才是咱们愿意之艺术。

$ node-echo Hello World

Linux

每当Linux系统下,我们得以将JS文件作为shell脚本来运行,从而达到上述目的,具体步骤如下:

  1. 当shell脚本中,可以由此#!注解来指定当前剧本使用的解析器。所以我们第一以node-echo.js文本顶部多以下一行注释,表明当前剧本使用NodeJS解析。

     #! /usr/bin/env node
    

    NodeJS会忽略掉放在JS模块首行的#!注解,不必顾虑这行注释是伪语句。

  2. 接下来,我们采取以下命令赋予node-echo.js文件执行权。

     $ chmod +x /home/user/bin/node-echo.js
    
  3. 终极,我们于PATH环境变量中指定的有目录下,例如当/usr/local/bin下面创建一个软链文件,文件称与我们想下的极命令同名,命令如下:

     $ sudo ln -s /home/user/bin/node-echo.js /usr/local/bin/node-echo
    

然处理后,我们就好当旁目录下利用node-echo命令了。

Windows

当Windows系统下之做法了不同,我们得仰仗.cmd文本来化解问题。假设node-echo.js存放在C:\Users\user\bin目,并且该目录就补充加到PATH环境变量里了。接下来要以拖欠目录下新建一个名为吧node-echo.cmd的文书,文件内容如下:

@node "C:\User\user\bin\node-echo.js" %*

诸如此类处理后,我们尽管好以外目录下行使node-echo命令了。

工程目录

了解了上述文化后,现在咱们得来圆地计划一个工程目录了。以编制一个发令行程序为条例,一般我们会又提供命令行模式以及API模式简单种下方式,并且我们会借助三正值担保来修代码。除了代码外,一个完好的次序为相应产生谈得来的文档和测试用例。因此,一个标准的工目录都扣留起像下这样。

- /home/user/workspace/node-echo/   # 工程目录
    - bin/                          # 存放命令行相关代码
        node-echo
    + doc/                          # 存放文档
    - lib/                          # 存放API相关代码
        echo.js
    - node_modules/                 # 存放三方包
        + argv/
    + tests/                        # 存放测试用例
    package.json                    # 元数据文件
    README.md                       # 说明文件

内有些文件内容如下:

/* bin/node-echo */
var argv = require('argv'),
    echo = require('../lib/echo');
console.log(echo(argv.join(' ')));

/ lib/echo.js /
module.exports = function (message) {
return message;
};

/ package.json /
{
“name”: “node-echo”,
“main”: “./lib/echo.js”
}

如上例子中分类存放了不同类别的文书,并由此node_moudles目录直接动用三正值包名加载模块。此外,定义了package.json之后,node-echo目为不过为看成一个包来使用。

NPM

NPM是连同NodeJS一起安装之承保管理工具,能化解NodeJS代码部署上的众题材,常见的用状况有以下几种:

  • 许用户从NPM服务器下充斥他人修的老三正担保到本地利用。

  • 容用户从NPM服务器下载并安装别人修的授命行程序到地头使用。

  • 同意用户以团结修的保险要指令行程序上传播NPM服务器供别人采取。

得看来,NPM建立了一个NodeJS生态圈,NodeJS开发者和用户可以内部互通有管。以下分别介绍这三种状况下什么使用NPM。

下载三正担保

急需采用三正值包时,首先得清楚发生什么包可用。虽然npmjs.org供了单搜索框可以依据包名来找,但要连想采取的老三正在担保之名还无确定的话,就请求百度瞬间吧。知道了包名后,比如上边例子中之argv,就好在工程目录下打开终端,使用以下命令来下充斥三方包。

$ npm install argv
...
argv@0.0.2 node_modules\argv

下载好下,argv担保就是厕了工程目录下之node_modules目录中,因此当代码中只是需要经过require('argv')的章程就是吓,无需指定三方担保路径。

以上命令默认下充斥最新版本三着担保,如果想使下载指定版本的话,可以在包名后止加上@<version>,例如通过以下命令可生充斥0.0.1本的argv

$ npm install argv@0.0.1
...
argv@0.0.1 node_modules\argv

比方采用及之老三在担保比多,在极端下一个承保一长命令地设置不休太人肉了。因此NPM对package.json的字段做了扩大,允许以中间发明三正值担保依赖。因此,上边例子中之package.json可以改写如下:

{
    "name": "node-echo",
    "main": "./lib/echo.js",
    "dependencies": {
        "argv": "0.0.2"
    }
}

这样处理后,在工程目录下就是足以使npm install一声令下批量装三正在担保了。更主要之是,当以后node-echo否上传到了NPM服务器,别人下载者包时,NPM会根据包中申明的老三正担保依赖自动下充斥进一步因的老三方包。例如,使用npm install node-echo一声令下时,NPM会自动创建以下目录结构。

- project/
    - node_modules/
        - node-echo/
            - node_modules/
                + argv/
            ...
    ...

如此一来,用户只待关注好一直用的老三着担保,不欲团结去化解有保险之仗关系。

装命令行程序

从今NPM服务达标下载安装一个发令行程序的措施与三正在担保类似。例如上例被之node-echo提供了命令行使用方法,只要node-echo和谐安排好了系的package.json字段,对于用户而言,只需要动用以下命令安装程序。

$ npm install node-echo -g

参数中之-g表示全局安装,因此node-echo会晤默认安装及以下职务,并且NPM会自动创建好Linux系统下得之软链文件或者Windows系统下要之.cmd文件。

- /usr/local/               # Linux系统下
    - lib/node_modules/
        + node-echo/
        ...
    - bin/
        node-echo
        ...
    ...

- %APPDATA%\npm\            # Windows系统下
    - node_modules\
        + node-echo\
        ...
    node-echo.cmd
    ...

颁发代码

第一坏下NPM发布代码前需报一个账号。终端下运作npm adduser,之后依提示做即可。账号搞定后,接着我们需要编制package.json文件,加入NPM必需的字段。接着上边node-echo的例子,package.json里少不了的字段如下。

{
    "name": "node-echo",           # 包名,在NPM服务器上须要保持唯一
    "version": "1.0.0",            # 当前版本号
    "dependencies": {              # 三方包依赖,需要指定包名和版本号
        "argv": "0.0.2"
      },
    "main": "./lib/echo.js",       # 入口模块位置
    "bin" : {
        "node-echo": "./bin/node-echo"      # 命令行程序名和主模块位置
    }
}

从此,我们就算可当package.json四方目录下运行npm publish发表代码了。

版本号

利用NPM下载和颁布代码时都见面沾到版本号。NPM使用语义版本号来治本代码,这里大概介绍一下。

语义版本号分为X.Y.Z其三个,分别表示主版本号、次版本号和补丁版本号。当代码变更时,版本号循以下标准更新。

+ 如果只是修复bug,需要更新Z位。

+ 如果是新增了功能,但是向下兼容,需要更新Y位。

+ 如果有大变动,向下不兼容,需要更新X位。

本子号出矣这保险后,在表明三在担保依赖时,除了可据让一个永恒版本号他,还而依让有范围的版本号。例如"argv": "0.0.x"意味着因让0.0.x铺天盖地之时版本argv。NPM支持之所有版本号范围点名方式得以查看官文档。

脑子一点

除去本章介绍的片外,NPM还提供了众多作用,package.json里也发生好多其他有效之字段。除了可以以npmjs.org/doc/翻开官方文档外,这里还介绍一些NPM常用命令。

  • NPM提供了好多限令,例如installpublish,使用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>得收回发布温馨发布过之之一版本代码。

小结

本章介绍了动NodeJS编写代码前待做的准备工作,总结起来有以下几点:

  • 编制代码前先行规划好目录结构,才能够形成有条不紊。

  • 小深把的先后可以拿代码拆分为多单模块管理,更老些的次可以以包来组织模块。

  • 客观运用node_modulesNODE_PATH来解耦包的利用方法同物理路径。

  • 行使NPM加入NodeJS生态圈互通有管。

  • 想到了向往的包名时请提前于NPM上抢注。

文本操作

为前者觉得假如得神器的非是NodeJS能召开网络编程,而是NodeJS能够操作文件。小至文件查找,大到代码编译,几乎从来不一个前端工具不操作文件。换个角度说,几乎为单独待部分数码处理逻辑,再长有文本操作,就可知编写出大部分前端工具。本章将介绍和的有关的NodeJS内置模块。

开门红

NodeJS提供了中心的文书操作API,但是比如文件拷贝这种高级功能就没有提供,因此我们事先以文件拷贝程序练手。与copy命令类似,我们的次第用能够接受源文件路径和对象文件路径两独参数。

聊文件拷贝

咱俩应用NodeJS内置的fs模块简单实现之顺序如下。

var fs = require('fs');

function copy(src, dst) {
fs.writeFileSync(dst, fs.readFileSync(src));
}

function main(argv) {
copy(argv[0], argv[1]);
}

main(process.argv.slice(2));

以上程序行使fs.readFileSync从今根源路读取文件内容,并采取fs.writeFileSync用文件内容写副目标路径。

豆知识:
process凡是一个全局变量,可经process.argv获取命令执行参数。由于argv[0]一贯等NodeJS执行顺序的绝对路径,argv[1]定点等主模块的绝对路径,因此首先只令执行参数从argv[2]是职位上马。

杀文件拷贝

上的主次拷贝一些有点文件并未啥问题,但这种一次性将具备文件内容还读取到内存中后还一次性写副磁盘的方式不入拷贝大文件,内存会爆仓。对于大文件,我们不得不读一些描写一些,直到好拷贝。因此上边的主次要改造如下。

var fs = require('fs');

function copy(src, dst) {
fs.createReadStream(src).pipe(fs.createWriteStream(dst));
}

function main(argv) {
copy(argv[0], argv[1]);
}

main(process.argv.slice(2));

以上程序用fs.createReadStream创办了一个源文件之一味读数据流,并采用fs.createWriteStream创立了一个目标文件之仅写数据流,并且因此pipe方法把少只数据流连接了起。连接起来后发出的作业,说得抽象点的话,水顺着水管从一个桶流到了任何一个桶。

API走马观花

俺们事先盖看看NodeJS提供了争与文书操作有关的API。这里并无逐一介绍每个API的运用办法,官方文档已经召开得挺好了。

Buffer(数据块)

合法文档: http://nodejs.org/api/buffer.html

JS语言自身就发字符串数据类型,没有二进制数据类型,因此NodeJS提供了一个跟String对顶之大局构造函数Buffer来供对二进制数据的操作。除了可读取文件得到Buffer的实例外,还能够直接组织,例如:

var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]);

Buffer暨字符串类似,除了可用.length属性得到字节长度外,还好用[index]方读取指定位置的字节,例如:

bin[0]; // => 0x68;

Buffer与字符串能够互相转化,例如可以以指定编码将二进制数据转发为字符串:

var str = bin.toString('utf-8'); // => "hello"

或转,将字符串转换为指定编码下之二进制数据:

var bin = new Buffer('hello', 'utf-8'); // => <Buffer 68 65 6c 6c 6f>

Buffer跟字符串有一个重中之重分。字符串是单念之,并且针对字符串的其它修改得的且是一个新字符串,原字符串保持无转换。至于Buffer,更如是可举行指针操作的C语言数组。例如,可以就此[index]艺术一直改动某位置的字节。

bin[0] = 0x48;

.slice主意为非是返回一个初的Buffer,而更如是归了赖为原Buffer高中级的某位置的指针,如下所示。

[ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]
    ^           ^
    |           |
   bin     bin.slice(2)

因此对.slice术返回的Buffer的修改会作用被原Buffer,例如:

var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]);
var sub = bin.slice(2);

sub[0] = 0x65;
console.log(bin); // =>

啊因此,如果想如果拷贝一卖Buffer,得首先创建一个新的Buffer,并通过.copy术把原Buffer饱受的数目复制过去。这个近乎于申请一片新的内存,并拿已经产生外存中的数目复制过去。以下是一个事例。

var bin = new Buffer([ 0x68, 0x65, 0x6c, 0x6c, 0x6f ]);
var dup = new Buffer(bin.length);

bin.copy(dup);
dup[0] = 0x48;
console.log(bin); // =>
console.log(dup); // =>

总之,Buffer将JS的数量处理能力由字符串扩展至了随机二进制数据。

Stream(数据流)

法定文档: http://nodejs.org/api/stream.html

当内存中无法同糟糕装下得处理的多少经常,或者单读取一边处理越快捷时,我们就是需要为此到数据流。NodeJS中通过各种Stream来供针对性数据流的操作。

如上边的挺文件拷贝程序吗条例,我们好啊数量出自创建一个只是念数据流,示例如下:

var rs = fs.createReadStream(pathname);

rs.on(‘data’, function (chunk) {
doSomething(chunk);
});

rs.on(‘end’, function () {
cleanUp();
});

豆知识:
Stream因事件机制工作,所有Stream的实例都蝉联给NodeJS提供的EventEmitter。

上边的代码中data事件会源源不断地受点,不管doSomething函数是否处理得过来。代码可以继续召开如下改造,以缓解这个题材。

var rs = fs.createReadStream(src);

rs.on(‘data’, function (chunk) {
rs.pause();
doSomething(chunk, function () {
rs.resume();
});
});

rs.on(‘end’, function () {
cleanUp();
});

如上代码给doSomething函数加上了回调,因此我们可以处理多少前暂停数据读取,并当拍卖数据后继续读取数据。

此外,我们也堪吗数目标创造一个单写数据流,示例如下:

var rs = fs.createReadStream(src);
var ws = fs.createWriteStream(dst);

rs.on(‘data’, function (chunk) {
ws.write(chunk);
});

rs.on(‘end’, function () {
ws.end();
});

我们把doSomething变成了向就写多少流里写副数据后,以上代码看起便比如是一个文书拷贝程序了。但是上述代码是上提到的题材,如果写副速度跟不上读取速度的说话,只写多少流内部的休养存会爆仓。我们可因.write措施的返值来判定传入的数额是写副目标了,还是临时在了缓存了,并依据drain事件来判断什么时偏偏写数据流已经用缓存中之数码勾勒副目标,可以传下一个亟待写多少了。因此代码可以改造如下:

var rs = fs.createReadStream(src);
var ws = fs.createWriteStream(dst);

rs.on(‘data’, function (chunk) {
if (ws.write(chunk) === false) {
rs.pause();
}
});

rs.on(‘end’, function () {
ws.end();
});

ws.on(‘drain’, function () {
rs.resume();
});

以上代码实现了数码从单独念数据流到仅仅写数据流的搬运,并包了防范爆仓控制。因为这种应用状况很多,例如头的那个文件拷贝程序,NodeJS直接提供了.pipe艺术来举行就档子业务,其内部贯彻方式跟上的代码类似。

File System(文件系统)

官方文档: http://nodejs.org/api/fs.html

NodeJS通过fs内置模块提供对文本之操作。fs模块提供的API基本上可以分为以下三近似:

  • 文件属性读写。

    其间常用之出fs.statfs.chmodfs.chown等等。

  • 文件内容念写。

    里常用之生fs.readFilefs.readdirfs.writeFilefs.mkdir等等。

  • 根文件操作。

    其中常用之出fs.openfs.readfs.writefs.close等等。

NodeJS最精华的异步IO模型在fs模块里有所充分的体现,例如头提到的这些API都通过回调函数传递结果。以fs.readFile为例:

fs.readFile(pathname, function (err, data) {
    if (err) {
        // Deal with error.
    } else {
        // Deal with data.
    }
});

如上边代码所示,基本上有fs模块API的回调参数都产生些许独。第一个参数在有荒唐有时相当异常对象,第二单参数始终用来返回API方法执行结果。

此外,fs模块的享有异步API都出对应之共版本,用于无法用异步操作时,或者同步操作更方便时之图景。同步API除了艺术名的结尾多矣一个Sync外,异常对象及实践结果的传递方式为闹对应变更。同样因为fs.readFileSync为例:

try {
    var data = fs.readFileSync(pathname);
    // Deal with data.
} catch (err) {
    // Deal with error.
}

fs模块提供的API很多,这里不一一介绍,需要经常请自行查阅官方文档。

Path(路径)

法定文档: http://nodejs.org/api/path.html

操作文件时不免不跟公事路径打交道。NodeJS提供了path坐模块来简化路径相关操作,并升级代码可读性。以下分别介绍几单常因此之API。

  • path.normalize

    以盛传的门径转换为正规路径,具体讲的讲话,除了解析路径中的...他,还能够去丢多余的斜杠。如果出先后要使用路径作为少数数据的目录,但同时允许用户擅自输入路径时,就待采取该方法保证路径的唯一性。以下是一个事例:

      var cache = {};
    

      function store(key, value) {
          cache[path.normalize(key)] = value;
      }
    

      store('foo/bar', 1);
      store('foo//baz//../bar', 2);
      console.log(cache);  // => { "foo/bar": 2 }
    

    坑有无顾:
    标准化后的路子里的斜杠在Windows系统下是\,而于Linux系统下是/。如果想管其他系统下还采取/作为路径分隔符的语句,需要为此.replace(/\\/g, '/')更交替一下专业路径。

  • path.join

    用盛传的大都独路子拼接为正规路径。该方法可避免手工拼接路径字符串的麻烦,并且能在不同体系下对利用相应的门径分隔符。以下是一个例子:

      path.join('foo/', 'baz/', '../bar'); // => "foo/bar"
    
  • path.extname

    当我们用基于不同文件扩展名做不同操作时,该方法就是显得异常好用。以下是一个例子:

      path.extname('foo/bar.js'); // => ".js"
    

path模块提供的旁方法为未多,稍微看一下法定文档就会一切左右。

遍历目录

遍历目录是操作文件时的一个科普需求。比如写一个主次,需要找到并处理指定目录下的拥有JS文件时,就待遍历整个目录。

递归算法

遍历目录时一般用递归算法,否则即不便编写出简洁之代码。递归算法和数学归纳法类似,通过不停压缩问题的层面来化解问题。以下示例说明了这种艺术。

function factorial(n) {
    if (n === 1) {
        return 1;
    } else {
        return n * factorial(n - 1);
    }
}

上的函数用于计算N的阶乘(N!)。可以看来,当N大于1时,问题简化为计算N乘以N-1的阶乘。当N等于1时,问题达成至极小框框,不待重新简化,因此直归1。

陷阱:
使用递归算法编写的代码虽然简单,但鉴于每递归一不成就时有发生相同坏函数调用,在急需先考虑性能时,需要将递归算法转换为循环算法,以缩减函数调用次数。

遍历算法

目是一个树状结构,在方方面面历时一般用深度优先+先先后遍历算法。深度优先,意味着到一个节点后,首先随着整个历子节点而未是邻里节点。先序遍历,意味着首不好至了有节点就算遍历完成,而休是终极一蹩脚回到某节点才算数。因此采取这种遍历方式时,下边这株树之遍历顺序是A > B > D > E > C > F

          A
         / \
        B   C
       / \   \
      D   E   F

同台遍历

叩问了必不可少之算法后,我们可简简单单地实现以下目录遍历函数。

function travel(dir, callback) {
    fs.readdirSync(dir).forEach(function (file) {
        var pathname = path.join(dir, file);

if (fs.statSync(pathname).isDirectory()) {
travel(pathname, callback);
} else {
callback(pathname);
}
});
}

可观看,该函数以某个目录作为遍历的起点。遇到一个子目录时,就优先就整个历子目录。遇到一个文书时,就拿公文之绝对路径传被回调函数。回调函数拿到文件路径后,就足以举行各种判断与拍卖。因此只要发生以下目录:

- /home/user/
    - foo/
        x.js
    - bar/
        y.js
    z.css

应用以下代码遍历该目录时,得到的输入如下。

travel('/home/user', function (pathname) {
    console.log(pathname);
});

/home/user/foo/x.js
/home/user/bar/y.js
/home/user/z.css

异步遍历

假设读取目录或读取文件状态时用的凡异步API,目录遍历函数实现起来会有点复杂,但原理完全同。travel函数的异步版本如下。

function travel(dir, callback, finish) {
    fs.readdir(dir, function (err, files) {
        (function next(i) {
            if (i < files.length) {
                var pathname = path.join(dir, files[i]);

fs.stat(pathname, function (err, stats) {
if (stats.isDirectory()) {
travel(pathname, callback, function () {
next(i + 1);
});
} else {
callback(pathname, function () {
next(i + 1);
});
}
});
} else {
finish && finish();
}
}(0));
});
}

这边不详细介绍异步遍历函数的编制技巧,在继续章节中会详细介绍这。总之我们可见到异步编程还是很复杂的。

文件编码

下NodeJS编写前端工具时,操作得最多之凡文件文件,因此也即涉嫌到了文件编码的处理问题。我们常因此的文本编码有UTF8GBK两种,并且UTF8文本还可能含有BOM。在读取不同编码的文本文件时,需要以文件内容转换为JS使用的UTF8编码字符串后才能够正常处理。

BOM的移除

BOM用于标记一个文本文件使用Unicode编码,其本人是一个Unicode字符(”\uFEFF”),位于文本文件头部。在不同之Unicode编码下,BOM字符对应的次上前制字节如下:

    Bytes      Encoding
----------------------------
    FE FF       UTF16BE
    FF FE       UTF16LE
    EF BB BF    UTF8

所以,我们得以依据文件文件头几乎个字节等于啥来判定文件是否包含BOM,以及用啊种Unicode编码。但是,BOM字符虽然从及了符号文件编码的图,其本身却非属文件内容之均等有的,如果读取文本文件时无失掉BOM,在好几使用状况下就算见面起问题。例如我们管几单JS文件合并成一个文本后,如果文件中含有BOM字符,就见面造成浏览器JS语法错误。因此,使用NodeJS读取文本文件时,一般要去掉BOM。例如,以下代码实现了辨认及去UTF8
BOM的成效。

function readText(pathname) {
    var bin = fs.readFileSync(pathname);

if (bin[0] === 0xEF && bin[1] === 0xBB && bin[2] === 0xBF) {
bin = bin.slice(3);
}

return bin.toString(‘utf-8’);
}

GBK转UTF8

NodeJS支持在读取文本文件时,或者在Buffer易为字符串时指定文本编码,但遗憾之是,GBK编码不在NodeJS自身支持范围外。因此,一般我们靠iconv-lite其一三着担保来换编码。使用NPM下充斥该包后,我们可遵循下方式编写一个读取GBK文本文件之函数。

var iconv = require('iconv-lite');

function readGBKText(pathname) {
var bin = fs.readFileSync(pathname);

return iconv.decode(bin, ‘gbk’);
}

单字节编码

有时候,我们鞭长莫及预知需要读取的文本采用哪种编码,因此为即无法指定正确的编码。比如我们而处理的一点CSS文件被,有的据此GBK编码,有的据此UTF8编码。虽然可得程度足以因文件之字节内容猜测出文本编码,但这里而介绍的是发头局限,但是倘若简单得几近之同一种技术。

首先我们领略,如果一个文件文件就含英文字符,比如Hello World,那无论是用GBK编码或是UTF8编码读取这个文件都是从未有过问题之。这是坐当这些编码下,ASCII0~128范围外字符都以同样的单字节编码。

反过来讲,即使一个文件文件中起中文等字符,如果我们需要处理的字符仅在ASCII0~128克外,比如除了注释和字符串以外的JS代码,我们尽管可统一用单字节编码来读取文件,不用关心文件之实际编码是GBK还是UTF8。以下示例说明了这种办法。

1. GBK编码源文件内容:
    var foo = '中文';
2. 对应字节:
    76 61 72 20 66 6F 6F 20 3D 20 27 D6 D0 CE C4 27 3B
3. 使用单字节编码读取后得到的内容:
    var foo = '{乱码}{乱码}{乱码}{乱码}';
4. 替换内容:
    var bar = '{乱码}{乱码}{乱码}{乱码}';
5. 使用单字节编码保存后对应字节:
    76 61 72 20 62 61 72 20 3D 20 27 D6 D0 CE C4 27 3B
6. 使用GBK编码读取后得到内容:
    var bar = '中文';

此地的窍门在于,不管大于0xEF的么字节在单字节编码下为分析成什么乱码字符,使用同样的单字节编码保留这些乱码字符时,背后对应的字节保持无换。

NodeJS中于带了平等种binary编码可以就此来兑现这措施,因此于下例中,我们采取这种编码来演示上例对应的代码该怎么写。

function replace(pathname) {
    var str = fs.readFileSync(pathname, 'binary');
    str = str.replace('foo', 'bar');
    fs.writeFileSync(pathname, str, 'binary');
}

小结

本章介绍了动NodeJS操作文件时得之API以及部分技术,总结起来有以下几点:

  • 学好文件操作,编写各种程序都不怕。

  • 假定非是不行以一点一滴性能,fs模块的同步API能给生更加光明。

  • 得针对文本读写得字节级别之鬼斧神工控制时,请动fs模块的文书底层操作API。

  • 毫无采取拼接字符串的办法来拍卖途径,使用path模块。

  • 操纵好目录遍历和文件编码处理技术,很实用。

网络操作

切莫打听网络编程的程序员不是好前端,而NodeJS恰好提供了同样扇了解网络编程的窗口。通过NodeJS,除了可以编制一些服务端程序来拉前端开发和测试外,还会上有些HTTP协议和Socket协议的有关知识,这些文化在优化前端性能及排查前端故障时可能能派上用场。本章将介绍与的休戚相关的NodeJS内置模块。

开门红

NodeJS本来的用途是编写高性能Web服务器。我们先是在此地更一下法定文档里之例证,使用NodeJS内置的http模块简单实现一个HTTP服务器。

var http = require('http');

http.createServer(function (request, response) {
response.writeHead(200, { ‘Content-Type’: ‘text-plain’ });
response.end(‘Hello World\n’);
}).listen(8124);

上述程序创建了一个HTTP服务器并监听8124端口,打开浏览器访问该端口http://127.0.0.1:8124/就是能够见到成效。

豆知识:
在Linux系统下,监听1024之下端口需要root权限。因此,如果想监听80要么443端口底说话,需要动用sudo命启动程序。

API走马观花

俺们先行盖看看NodeJS提供了哪与网操作有关的API。这里并无逐一介绍每个API的采用办法,官方文档已经召开得稀好了。

HTTP

合法文档: http://nodejs.org/api/http.html

‘http’模块提供零星栽采取方法:

  • 作服务端使用时,创建一个HTTP服务器,监听HTTP客户端请求并返响应。

  • 作为客户端采用时,发起一个HTTP客户端请求,获取服务端响应。

先是我们来探服务端模式下怎么工作。如开门红中之例证所示,首先需利用.createServer措施创建一个服务器,然后调用.listen方监听端口。之后,每当来了一个客户端请求,创建服务器时传入的回调函数就吃调用一蹩脚。可以看到,这是平等栽事件机制。

HTTP请求精神上是一个数据流,由请求求头(headers)和请求体(body)组成。例如以下是一个完完全全的HTTP请求数据内容。

POST / HTTP/1.1
User-Agent: curl/7.26.0
Host: localhost
Accept: */*
Content-Length: 11
Content-Type: application/x-www-form-urlencoded

Hello World

得看到,空行之上是告求头,之下是请求求体。HTTP请求于发送给服务器时,可以认为是本从头到尾的一一一个字节一个字节地为数量流方式发送的。而http模块创建的HTTP服务器在接受及完全的请求头晚,就会调用回调函数。在回调函数中,除了可以使request目标看请求求头数据外,还能将request对象当作一个仅仅念数据流来访问请求求体数据。以下是一个例。

http.createServer(function (request, response) {
    var body = [];

console.log(request.method);
console.log(request.headers);

request.on(‘data’, function (chunk) {
body.push(chunk);
});

request.on(‘end’, function () {
body = Buffer.concat(body);
console.log(body.toString());
});
}).listen(80);

POST
{ 'user-agent': 'curl/7.26.0',
  host: 'localhost',
  accept: '*/*',
  'content-length': '11',
  'content-type': 'application/x-www-form-urlencoded' }
Hello World

HTTP响应本质上吧是一个数据流,同样由于响应头(headers)和响应体(body)组成。例如以下是一个完整的HTTP请求数据内容。

HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 11
Date: Tue, 05 Nov 2013 05:31:38 GMT
Connection: keep-alive

Hello World

以回调函数中,除了可以利用response目标来形容入响应头数据外,还能够拿response目标当作一个光写多少流来写副响应体数据。例如在偏下例子中,服务端原样将客户端请求的请求体数据返回给客户端。

http.createServer(function (request, response) {
    response.writeHead(200, { 'Content-Type': 'text/plain' });

request.on(‘data’, function (chunk) {
response.write(chunk);
});

request.on(‘end’, function () {
response.end();
});
}).listen(80);

连下我们看客户端模式下怎样工作。为了提倡一个客户端HTTP请求,我们用指定目标服务器的职位并发送请求头和请求体,以下示例演示了具体做法。

var options = {
        hostname: 'www.example.com',
        port: 80,
        path: '/upload',
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        }
    };

var request = http.request(options, function (response) {});

request.write(‘Hello World’);
request.end();

得看来,.request方创建了一个客户端,并指定要目标和伸手求头数据。之后,就可以拿request靶当作一个就写多少流来写副请求体数据及得了请求。另外,由于HTTP请求中GET告是太广的均等栽,并且不待请求体,因此http模块也提供了以下即捷API。

http.get('http://www.example.com/', function (response) {});

当客户端发送请求并收取至整的服务端响应头时,就会调用回调函数。在回调函数中,除了可以使response目标看响应头数据外,还能将response对象当作一个止念数据流来访问响应体数据。以下是一个事例。

http.get('http://www.example.com/', function (response) {
    var body = [];

console.log(response.statusCode);
console.log(response.headers);

response.on(‘data’, function (chunk) {
body.push(chunk);
});

response.on(‘end’, function () {
body = Buffer.concat(body);
console.log(body.toString());
});
});

200
{ 'content-type': 'text/html',
  server: 'Apache',
  'content-length': '801',
  date: 'Tue, 05 Nov 2013 06:08:41 GMT',
  connection: 'keep-alive' }
<!DOCTYPE html>
...

HTTPS

法定文档: http://nodejs.org/api/https.html

https模块与http模块极为类似,区别在于https模块需要额外处理SSL证书。

以服务端模式下,创建一个HTTPS服务器的示范如下。

var options = {
        key: fs.readFileSync('./ssl/default.key'),
        cert: fs.readFileSync('./ssl/default.cer')
    };

var server = https.createServer(options, function (request, response) {
// …
});

足见到,与创造HTTP服务器相比,多矣一个options对象,通过keycert字段指定了HTTPS服务器使用的私钥和公钥。

另外,NodeJS支持SNI技术,可以根据HTTPS客户端请求使用的域名动态下不同的证书,因此与一个HTTPS服务器可以用多单域名提供劳动。接着上例,可以采取以下方法吗HTTPS服务器添加多组关系。

server.addContext('foo.com', {
    key: fs.readFileSync('./ssl/foo.com.key'),
    cert: fs.readFileSync('./ssl/foo.com.cer')
});

server.addContext(‘bar.com’, {
key: fs.readFileSync(‘./ssl/bar.com.key’),
cert: fs.readFileSync(‘./ssl/bar.com.cer’)
});

每当客户端模式下,发起一个HTTPS客户端请求和http模块几乎一致,示例如下。

var options = {
        hostname: 'www.example.com',
        port: 443,
        path: '/',
        method: 'GET'
    };

var request = https.request(options, function (response) {});

request.end();

但倘若目标服务器使用的SSL证书是自制的,不是起披露机构购买的,默认情况下https模块会拒绝连接,提示说发生证安全题材。在options里加入rejectUnauthorized: false字段可以禁用对关系中的反省,从而允许https模块请求支付环境下使用自制证书的HTTPS服务器。

URL

官文档: http://nodejs.org/api/url.html

处理HTTP请求时url模块使用率超高,因为该模块允许解析URL、生成URL,以及拼接URL。首先我们来看看一个完全的URL的各有。

                           href
 -----------------------------------------------------------------
                            host              path
                      --------------- ----------------------------
 http: // user:pass @ host.com : 8080 /p/a/t/h ?query=string #hash
 -----    ---------   --------   ---- -------- ------------- -----
protocol     auth     hostname   port pathname     search     hash
                                                ------------
                                                   query

咱得以采用.parse法来拿一个URL字符串转换为URL对象,示例如下。

url.parse('http://user:pass@host.com:8080/p/a/t/h?query=string#hash');
/* =>
{ protocol: 'http:',
  auth: 'user:pass',
  host: 'host.com:8080',
  port: '8080',
  hostname: 'host.com',
  hash: '#hash',
  search: '?query=string',
  query: 'query=string',
  pathname: '/p/a/t/h',
  path: '/p/a/t/h?query=string',
  href: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash' }
*/

传给.parse方式的未必然要是一个整体的URL,例如当HTTP服务器回调函数中,request.url莫带有协议头和域名,但同样好就此.parse办法分析。

http.createServer(function (request, response) {
    var tmp = request.url; // => "/foo/bar?a=b"
    url.parse(tmp);
    /* =>
    { protocol: null,
      slashes: null,
      auth: null,
      host: null,
      port: null,
      hostname: null,
      hash: null,
      search: '?a=b',
      query: 'a=b',
      pathname: '/foo/bar',
      path: '/foo/bar?a=b',
      href: '/foo/bar?a=b' }
    */
}).listen(80);

.parse方还支持第二单同老三只布尔类型可选参数。第二个参数等于true时不时,该法返回的URL对象吃,query字段不再是一个字符串,而是一个透过querystring模块转换后底参数对象。第三只参数等于true每每,该办法可以对解析不带来协议头的URL,例如//www.example.com/foo/bar

反过来,format措施允许以一个URL对象转换为URL字符串,示例如下。

url.format({
    protocol: 'http:',
    host: 'www.example.com',
    pathname: '/p/a/t/h',
    search: 'query=string'
});
/* =>
'http://www.example.com/p/a/t/h?query=string'
*/

另外,.resolve主意好用来拼接URL,示例如下。

url.resolve('http://www.example.com/foo/bar', '../baz');
/* =>
http://www.example.com/baz
*/

Query String

法定文档: http://nodejs.org/api/querystring.html

querystring模块用于落实URL参数字符串与参数对象的相互转换,示例如下。

querystring.parse('foo=bar&baz=qux&baz=quux&corge');
/* =>
{ foo: 'bar', baz: ['qux', 'quux'], corge: '' }
*/

querystring.stringify({ foo: ‘bar’, baz: [‘qux’, ‘quux’], corge: ” });
/ =>
‘foo=bar&baz=qux&baz=quux&corge=’
/

Zlib

法定文档: http://nodejs.org/api/zlib.html

zlib模块提供了数据压缩和解压的效力。当我们处理HTTP请求与应时,可能用用到此模块。

率先我们看一个动zlib模块压缩HTTP响应体数据的事例。这个事例中,判断了客户端是不是支持gzip,并在支撑之景况下行使zlib模块返回gzip之后的响应体数据。

http.createServer(function (request, response) {
    var i = 1024,
        data = '';

while (i–) {
data += ‘.’;
}

if ((request.headers[‘accept-encoding’] || ”).indexOf(‘gzip’) !== -1) {
zlib.gzip(data, function (err, data) {
response.writeHead(200, {
‘Content-Type’: ‘text/plain’,
‘Content-Encoding’: ‘gzip’
});
response.end(data);
});
} else {
response.writeHead(200, {
‘Content-Type’: ‘text/plain’
});
response.end(data);
}
}).listen(80);

跟着我们看一个采取zlib模块解压HTTP响应体数据的事例。这个事例中,判断了服务端响应是否用gzip压缩,并当减少的场面下采取zlib模块解压响应体数据。

var options = {
        hostname: 'www.example.com',
        port: 80,
        path: '/',
        method: 'GET',
        headers: {
            'Accept-Encoding': 'gzip, deflate'
        }
    };

http.request(options, function (response) {
var body = [];

response.on(‘data’, function (chunk) {
body.push(chunk);
});

response.on(‘end’, function () {
body = Buffer.concat(body);

if (response.headers[‘content-encoding’] === ‘gzip’) {
zlib.gunzip(body, function (err, data) {
console.log(data.toString());
});
} else {
console.log(data.toString());
}
});
}).end();

Net

官方文档: http://nodejs.org/api/net.html

net模块可用来创造Socket服务器或Socket客户端。由于Socket在前端领域的行使限制还不是非常广阔,这里先不涉到WebSocket的牵线,仅仅略演示一下怎么样由Socket层面来落实HTTP请求和应。

首先我们来拘禁一个使Socket搭建筑一个颇无严谨的HTTP服务器的事例。这个HTTP服务器无收到什么要,都稳定返回相同的响应。

net.createServer(function (conn) {
    conn.on('data', function (data) {
        conn.write([
            'HTTP/1.1 200 OK',
            'Content-Type: text/plain',
            'Content-Length: 11',
            '',
            'Hello World'
        ].join('\n'));
    });
}).listen(80);

随即我们来拘禁一个动Socket发起HTTP客户端请求的例子。这个例子中,Socket客户端在确立连接后发送了一个HTTP
GET请求,并经过data事件监听函数来抱服务器响应。

var options = {
        port: 80,
        host: 'www.example.com'
    };

var client = net.connect(options, function () {
client.write([
‘GET / HTTP/1.1’,
‘User-Agent: curl/7.26.0’,
‘Host: www.baidu.com’,
‘Accept: /‘,
”,

].join(‘\n’));
});

client.on(‘data’, function (data) {
console.log(data.toString());
client.end();
});

头脑一点

采取NodeJS操作网络,特别是操作HTTP请求和应时见面逢有些惊喜,这里针对一些广大问题举行解答。

  • 问问:
    为什么通过headers靶看到之HTTP请求头或响应头字段不是驼峰的?

    报经:
    从标准及说,HTTP请求头和应头字段都应该是驼峰的。但实际是残忍的,不是每个HTTP服务端或客户端程序都严格按照规范,所以NodeJS在处理从别的客户端或服务端收到的头字段经常,都统一地换为小写字母格式,以便开发者能够应用统一之法子来访问头字段,例如headers['content-length']

  • 问:
    为什么http模块创建的HTTP服务器返回的应是chunked传输方式的?

    报经:
    因为默认情况下,使用.writeHead主意勾勒入响应头后,允许以.write术勾勒副随便长度的响应体数据,并下.end方法了一个响应。由于响应体数据长度不确定,因此NodeJS自动在响应头里补充加了Transfer-Encoding: chunked字段,并采用chunked传输方式。但是当响应体数据长度确定时,可利用.writeHead办法以响应头里加上Content-Length字段,这样做下NodeJS就不会见自动抬高Transfer-Encoding字段和行使chunked传输方式。

  • 叩问:
    为什么使用http模块发起HTTP客户端请求时,有时候会起socket hang up错误?

    答:
    发起客户端HTTP请求前要先创造一个客户端。http模块提供了一个大局客户端http.globalAgent,可以给咱们采取.request.get艺术时绝不手动创建客户端。但是全局客户端默认只同意5独连发Socket连接,当某一个时刻HTTP客户端请求创建了多,超过这个数字时,就会时有发生socket hang up荒谬。解决智为特别简短,通过http.globalAgent.maxSockets性将这数字改不行来即可。另外,https模块遇到是题材时常也一样通过https.globalAgent.maxSockets性来拍卖。

小结

本章介绍了采用NodeJS操作网络时需的API以及有坑回避技巧,总结起来有以下几点:

  • httphttps模块支持服务端模式与客户端模式简单种植使方法。

  • requestresponse靶除了用于读写头数据外,都得当数据流来操作。

  • url.parse法加上request.url属性是拍卖HTTP请求时之稳定搭配。

  • 使用zlib模块可减掉用HTTP协议时的多少传输量。

  • 通过net模块的Socket服务器和客户端可针对HTTP协议举行底层操作。

  • 小心踩坑。

过程管理

NodeJS可以感知与操纵我进程的周转环境暨状态,也可以创建子进程并跟那协同工作,这令NodeJS可以管多独程序组合在一起共同完成某项工作,并于中间充当胶水和调度器的打算。本章除了介绍及之相关的NodeJS内置模块外,还见面要介绍典型的动状况。

开门红

俺们已知晓了NodeJS自带的fs模块于基础,把一个索引里之具备文件及子目录都拷贝到任何一个目里要写过多代码。另外我们为晓得,终端下之cp指令于好用,一长达cp -r source/* target指令就能搞定目录拷贝。那我们先是看如何用NodeJS调用极命令来简化目录拷贝,示例代码如下:

var child_process = require('child_process');
var util = require('util');

function copy(source, target, callback) {
child_process.exec(
util.format(‘cp -r %s/* %s’, source, target), callback);
}

copy(‘a’, ‘b’, function (err) {
// …
});

自上述代码中好看看,子进程是异步运行的,通过回调函数返回执行结果。

API走马观花

俺们先行盖看看NodeJS提供了怎样与进程管理有关的API。这里并无逐一介绍每个API的以方法,官方文档已经做得非常好了。

Process

官文档: http://nodejs.org/api/process.html

其余一个历程都起起步进程时使用的吩咐执行参数,有规范输入标准输出,有运行权限,有运行环境和运行状态。在NodeJS中,可以透过process对象感知与控制NodeJS自身进程的所有。另外待小心的是,process免是搭模块,而是一个大局对象,因此当旁地方还得以一直动用。

Child Process

法定文档: http://nodejs.org/api/child_process.html

使用child_process模块可创造和控制子进程。该模块提供的API中最为核心的是.spawn,其余API都是对一定使用状况对其的愈益封装,算是一种语法糖。

Cluster

官文档: http://nodejs.org/api/cluster.html

cluster模块是针对性child_process模块的越封装,专用于解决光进程NodeJS
Web服务器无法充分利用多核CPU的问题。使用该模块可简化多进程服务器程序的支出,让每个核上运行一个工作过程,并联合通过主进程监听端口与分发请求。

运用场景

同进程管理有关的API单独介绍起较单调,因此此打局部独立的应用场景出发,分别介绍部分至关重要API的运用方法。

什么收获命令执行参数

以NodeJS中可由此process.argv抱命令执行参数。但是比奇怪之是,node推行顺序路径和主模块文件路径固定占据了argv[0]argv[1]点滴单位置,而首先独令执行参数从argv[2]开始。为了让argv采取起来越自然,可以以以下方法处理。

function main(argv) {
    // ...
}

main(process.argv.slice(2));

何以退出程序

一般而言一个先后召开得了所有工作后即便如常退出了,这时程序的退出状态码为0。或者一个程序运行时产生了十分后即便挂了,这时程序的退出状态码不对等0。如果我们当代码中抓获了有大,但是觉得程序不应有继承运行下去,需要及时退出,并且用拿退出状态码设置也指定数字,比如1,就得按照以下方法:

try {
    // ...
} catch (err) {
    // ...
    process.exit(1);
}

安支配输入输出

NodeJS程序的专业输入流(stdin)、一个正规输出流(stdout)、一个正经错误流(stderr)分别对应process.stdinprocess.stdoutprocess.stderr,第一独凡是单念数据流,后止两只凡是止写数据流,对它的操作以对数据流的操作办法即可。例如,console.log足以以下措施实现。

function log() {
    process.stdout.write(
        util.format.apply(util, arguments) + '\n');
}

什么样降权

于Linux系统下,我们解用采用root权限才会监听1024以下端口。但是若好端口监听后,继续被程序运行在root权限下存在安全隐患,因此最好好会拿权力降下来。以下是如此一个例证。

http.createServer(callback).listen(80, function () {
    var env = process.env,
        uid = parseInt(env['SUDO_UID'] || process.getuid(), 10),
        gid = parseInt(env['SUDO_GID'] || process.getgid(), 10);

process.setgid(gid);
process.setuid(uid);
});

达例被生出几乎触及得专注:

  1. 如果是经sudo赢得root权限的,运行程序的用户的UID和GID保存在环境变量SUDO_UIDSUDO_GID里面。如果是经过chmod +s艺术取得root权限的,运行程序的用户之UID和GID可一直通过process.getuidprocess.getgid术赢得。

  2. process.setuidprocess.setgid计只有领number列的参数。

  3. 降权时务必事先退GID再跌UID,否则顺序反过来的言辞就是无权力更改程序的GID了。

哪些创建子进程

以下是一个创建NodeJS子进程的例证。

var child = child_process.spawn('node', [ 'xxx.js' ]);

child.stdout.on(‘data’, function (data) {
console.log(‘stdout: ‘ + data);
});

child.stderr.on(‘data’, function (data) {
console.log(‘stderr: ‘ + data);
});

child.on(‘close’, function (code) {
console.log(‘child process exited with code ‘ + code);
});

达到例被以了.spawn(exec, args, options)办法,该方式支持三只参数。第一独参数是行文书路径,可以是推行文书之对立要绝对路径,也得是冲PATH环境变量能找到的实践文书称。第二只参数中,数组中之每个成员还按梯次对应一个下令执行参数。第三单参数可挑选,用于配置子进程的履环境及行为。

除此以外,上例被虽然通过子进程对象的.stdout.stderr访问子进程的出口,但经过options.stdio字段的不等安排,可以将子进程的输入输出重定向到其他数据流上,或者让子进程同享父进程的正经输入输出流,或者直接忽略子进程的输入输出。

过程之中如何报道

于Linux系统下,进程中可以透过信号互相通信。以下是一个例。

/* parent.js */
var child = child_process.spawn('node', [ 'child.js' ]);

child.kill(‘SIGTERM’);

/ child.js /
process.on(‘SIGTERM’, function () {
cleanUp();
process.exit(0);
});

当上例中,父进程经过.kill措施向子进程发送SIGTERM信号,子进程监听process对象的SIGTERM事件响应信号。不要让.kill道的号迷惑了,该法本质上是为此来深受进程发送信号的,进程收到信号后实际而举行吗,完全在于信号的种类与经过本身之代码。

此外,如果父子进程都是NodeJS进程,就可通过IPC(进程中通讯)双向传递数据。以下是一个例证。

/* parent.js */
var child = child_process.spawn('node', [ 'child.js' ], {
        stdio: [ 0, 1, 2, 'ipc' ]
    });

child.on(‘message’, function (msg) {
console.log(msg);
});

child.send({ hello: ‘hello’ });

/ child.js /
process.on(‘message’, function (msg) {
msg.hello = msg.hello.toUpperCase();
process.send(msg);
});

得视,父进程在创建子进程时,在options.stdio字段中通过ipc展了平长达IPC通道,之后便可以监听子进程对象的message事件接受来自子进程的信息,并经过.send道给子进程发送信息。在分层进程就边,可以当process靶及监听message事件接受来自父亲进程的信,并由此.send措施向爸爸进程发送信息。数据以传递过程被,会先行以殡葬端采用JSON.stringify道序列化,再于接收端使用JSON.parse措施反序列化。

争近乎护子进程

护理进程一般用来监控工作历程的周转状态,在劳作进程不正常退出时还开工作进程,保障工作经过不暂停运行。以下是一律种实现方式。

/* daemon.js */
function spawn(mainModule) {
    var worker = child_process.spawn('node', [ mainModule ]);

worker.on(‘exit’, function (code) {
if (code !== 0) {
spawn(mainModule);
}
});
}

spawn(‘worker.js’);

足看出,工作经过非正常退出时,守护进程就还开工作经过。

小结

本章介绍了利用NodeJS管理过程时得的API以及重要的运场景,总结起来有以下几点:

  • 使用process对象管理自己。

  • 使用child_process模块创建和管理子进程。

异步编程

NodeJS最要命之卖点——事件机制和异步IO,对开发者并无是晶莹剔透的。开发者需要按异步方式编写代码才故得达之卖点,而当时或多或少吗吃了有些NodeJS反对者的攻击。但无论如何,异步编程确实是NodeJS最酷之特征,没有掌握异步编程就未克算得真正学会了NodeJS。本章将介绍及异步编程相关的各种文化。

回调

在代码中,异步编程的第一手体现就是是回调。异步编程依托于回调来贯彻,但未克说用了回调后先后即使异步化了。我们第一可以省以下代码。

function heavyCompute(n, callback) {
    var count = 0,
        i, j;

for (i = n; i > 0; –i) {
for (j = n; j > 0; –j) {
count += 1;
}
}

callback(count);
}

heavyCompute(10000, function (count) {
console.log(count);
});

console.log(‘hello’);

— Console ——————————
100000000
hello

可见到,以上代码中的回调函数仍然先于后续代码执行。JS本身是单线程运行的,不可能以同等截代码还免竣工运行时错过运转别的代码,因此为即无设有异步执行的定义。

而是,如果某函数做的政工是创办一个别的线程或进程,并跟JS主线程并行地举行片事情,并于业务做得了晚通报JS主线程,那情景又未平等了。我们就看看以下代码。

setTimeout(function () {
    console.log('world');
}, 1000);

console.log(‘hello’);

— Console ——————————
hello
world

这次可以看出,回调函数后吃继续代码执行了。如同上边所说,JS本身是单线程的,无法异步执行,因此我们可当setTimeout当下看似JS规范以外的由运行环境提供的特有函数做的业务是开创一个平行线程后立即赶回,让JS主进程可以就执行后续代码,并于收受平行进程的打招呼后再次实行回调函数。除了setTimeoutsetInterval这些常见的,这仿佛函数还连NodeJS提供的比如fs.readFile等等的异步API。

此外,我们还回到JS是单线程运行的这个实际上,这决定了JS在推行了一段落代码之前无法推行包括回调函数在内的别的代码。也就是说,即使平行线程完成工作了,通知JS主线程执行回调函数了,回调函数也只要对等及JS主线程空闲时才能够开始推行。以下即是如此一个例证。

function heavyCompute(n) {
    var count = 0,
        i, j;

for (i = n; i > 0; –i) {
for (j = n; j > 0; –j) {
count += 1;
}
}
}

var t = new Date();

setTimeout(function () {
console.log(new Date() – t);
}, 1000);

heavyCompute(50000);

— Console ——————————
8520

可看看,本来当在1秒后让调用的回调函数因为JS主线程忙于运行其它代码,实际执行时间让大幅推。

代码设计模式

异步编程有那么些有意识的代码设计模式,为了实现均等的机能,使用并方式与异步方式编写的代码会起坏挺差异。以下分别介绍部分广泛的模式。

函数返回值

以一个函数的出口作为其它一个函数的输入是蛮广泛的要求,在同步方式下一般以以下方式编写代码:

var output = fn1(fn2('input'));
// Do something.

假如在异步方式下,由于函数执行结果未是透过返回值,而是经过回调函数传递,因此一般照以下办法编写代码:

fn2('input', function (output2) {
    fn1(output2, function (output1) {
        // Do something.
    });
});

得看来,这种艺术就是是一个回调函数模仿一个回调函多,套得最多矣深轻写起>形态的代码。

遍历数组

在遍历数组时,使用有函数依次对数码成员召开一些处理吧是常见的要求。如果函数是同步施行之,一般就是见面刻画有以下代码:

var len = arr.length,
    i = 0;

for (; i < len; ++i) { arr[i] = sync(arr[i]); }
// All array items have processed.

若果函数是异步执行的,以上代码就无法确保循环结束晚具备数组成员都处理完毕了。如果数组成员要一个搭一个串行处理,则相似按照以下办法编写异步代码:

(function next(i, len, callback) {
    if (i < len) {
        async(arr[i], function (value) {
            arr[i] = value;
            next(i + 1, len, callback);
        });
    } else {
        callback();
    }
}(0, arr.length, function () {
    // All array items have processed.
}));

好看来,以上代码在异步函数执行同样浅并返执行结果后才传入下一个数组成员并开始下一致轮子执行,直到有数组成员处理完毕后,通过回调的章程触发后续代码的执行。

若是数组成员好并行处理,但持续代码仍然需要持有数组成员处理完毕后才能够实施的话,则异步代码会调整成以下形式:

(function (i, len, count, callback) {
    for (; i < len; ++i) {
        (function (i) {
            async(arr[i], function (value) {
                arr[i] = value;
                if (++count === len) {
                    callback();
                }
            });
        }(i));
    }
}(0, arr.length, 0, function () {
    // All array items have processed.
}));

足看,与异步串行遍历的本对照,以上代码并行处理所有数组成员,并通过计数器变量来判定什么时候有数组成员都处理完毕了。

颇处理

JS自身提供的特别捕获和处理体制——try..catch..,只能用于共同实施之代码。以下是一个例。

function sync(fn) {
    return fn();
}

try {
sync(null);
// Do something.
} catch (err) {
console.log(‘Error: %s’, err.message);
}

— Console ——————————
Error: object is not a function

可见到,异常会沿着代码执行路径一直冒泡,直到撞第一单try说话时于抓走住。但由异步函数会打断代码执行路径,异步函数执行进程遭到及实施后发的坏冒泡到执行路径为打断的岗位时,如果一直未曾碰到try言语,就视作一个大局十分抛出。以下是一个事例。

function async(fn, callback) {
    // Code execution path breaks here.
    setTimeout(function () {
        callback(fn());
    }, 0);
}

try {
async(null, function (data) {
// Do something.
});
} catch (err) {
console.log(‘Error: %s’, err.message);
}

— Console ——————————
/home/user/test.js:4
callback(fn());
^
TypeError: object is not a function
at null._onTimeout (/home/user/test.js:4:13)
at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)

盖代码执行路径为由断了,我们便得以深冒泡到断点之前用try语把老捕获住,并经过回调函数传递给抓走的死。于是我们可以像下这样改造上的例子。

function async(fn, callback) {
    // Code execution path breaks here.
    setTimeout(function () {
        try {
            callback(null, fn());
        } catch (err) {
            callback(err);
        }
    }, 0);
}

async(null, function (err, data) {
if (err) {
console.log(‘Error: %s’, err.message);
} else {
// Do something.
}
});

— Console ——————————
Error: object is not a function

得看到,异常再次落网获住了。在NodeJS中,几乎拥有异步API都循以上措施设计,回调函数中第一只参数还是err。因此我们当编排好之异步函数时,也足以遵循这种方法来拍卖非常,与NodeJS的计划风格保持一致。

来矣那个处理方式后,我们随后可以想同一想一般我们是怎形容代码的。基本上,我们的代码都是做有工作,然后调用一个函数,然后再度举行有事情,然后重新调用一个函数,如此循环往复。如果我们写的凡同代码,只待在代码入口点写一个try言辞就可知捕获所有冒泡上来的不可开交,示例如下。

function main() {
    // Do something.
    syncA();
    // Do something.
    syncB();
    // Do something.
    syncC();
}

try {
main();
} catch (err) {
// Deal with exception.
}

但是,如果我们刻画的是异步代码,就惟有呵呵了。由于每次异步函数调用都见面阻塞代码执行路径,只能通过回调函数来传递异常,于是我们不怕用在每个回调函数里判断是否来格外来,于是光所以三次异步函数调用,就会发下这种代码。

function main(callback) {
    // Do something.
    asyncA(function (err, data) {
        if (err) {
            callback(err);
        } else {
            // Do something
            asyncB(function (err, data) {
                if (err) {
                    callback(err);
                } else {
                    // Do something
                    asyncC(function (err, data) {
                        if (err) {
                            callback(err);
                        } else {
                            // Do something
                            callback(null);
                        }
                    });
                }
            });
        }
    });
}

main(function (err) {
if (err) {
// Deal with exception.
}
});

好看来,回调函数已经深受代码变得复杂了,而异步方式下本着好的拍卖还强化了代码的复杂度。如果NodeJS的极其深卖点最后成为这个法,那就是无人乐于为此NodeJS了,因此接下会介绍NodeJS提供的有解决方案。

域(Domain)

法定文档: http://nodejs.org/api/domain.html

NodeJS提供了domain模块,可以简化异步代码的死去活来处理。在介绍该模块之前,我们要首先知道“域”的定义。简单的说话,一个域不怕是一个JS运行环境,在一个运行环境面临,如果一个非常没有叫抓走,将作一个大局十分被抛来。NodeJS通过process目标提供了捕获全局十分的方,示例代码如下

process.on('uncaughtException', function (err) {
    console.log('Error: %s', err.message);
});

setTimeout(function (fn) {
fn();
});

— Console ——————————
Error: undefined is not a function

则全局十分来只地方得捕获了,但是对绝大多数生,我们愿意尽快捕获,并冲结果决定代码的实践路径。我们用来下HTTP服务器代码作为例子:

function async(request, callback) {
    // Do something.
    asyncA(request, function (err, data) {
        if (err) {
            callback(err);
        } else {
            // Do something
            asyncB(request, function (err, data) {
                if (err) {
                    callback(err);
                } else {
                    // Do something
                    asyncC(request, function (err, data) {
                        if (err) {
                            callback(err);
                        } else {
                            // Do something
                            callback(null, data);
                        }
                    });
                }
            });
        }
    });
}

http.createServer(function (request, response) {
async(request, function (err, data) {
if (err) {
response.writeHead(500);
response.end();
} else {
response.writeHead(200);
response.end(data);
}
});
});

以上代码用呼吁对象交给异步函数处理后,再因处理结果返回响应。这里运用了应用回调函数传递异常的方案,因此async函数内部如果再次多几独异步函数调用的口舌,代码就变成上这副鬼样子了。为了让代码好看点,我们可以当各个处理一个请求时,使用domain模块创建一个子域(JS子运行条件)。在子域内运行的代码可以无限制丢来大,而这些异常可以通过子域对象的error事件联合捕获。于是以上代码可以做如下改造:

function async(request, callback) {
    // Do something.
    asyncA(request, function (data) {
        // Do something
        asyncB(request, function (data) {
            // Do something
            asyncC(request, function (data) {
                // Do something
                callback(data);
            });
        });
    });
}

http.createServer(function (request, response) {
var d = domain.create();

d.on(‘error’, function () {
response.writeHead(500);
response.end();
});

d.run(function () {
async(request, function (data) {
response.writeHead(200);
response.end(data);
});
});
});

可见见,我们采用.create措施创建了一个子域对象,并由此.run方上待在子域中运作的代码的入口点。而坐落子域中的异步函数回调函数由于不再用捕获异常,代码一下子瘦身很多。

陷阱

任通过process对象的uncaughtException事件捕获到全局十分,还是通过子域对象的error事件捕获到了子域异常,在NodeJS官方文档里都强烈建议处理完毕异常后及时又开程序,而未是于程序继续运行。按照官方文档的布道,发生甚后的主次处于一个未确定的运行状态,如果无就退出的话,程序可能会见生出严重内存泄漏,也或呈现得老大奇怪。

唯独此间要澄清一些实。JS本身的throw..try..catch那个处理体制并无会见导致内存泄漏,也无见面受程序的施行结果意外,但NodeJS并无是存粹的JS。NodeJS里大量的API内部是故C/C++实现的,因此NodeJS程序的运转过程遭到,代码执行路径穿梭于JS引擎内部以及外部,而JS的酷抛出机制可能会见死正常的代码执行流程,导致C/C++部分的代码表现很,进而导致内存泄漏等问题。

因此,使用uncaughtExceptiondomain抓获异常,代码执行路径里提到到了C/C++部分的代码时,如果未能够确定是不是会招内存泄漏等问题,最好以处理终结异常后再也开程序于妥当。而利用try告句捕获异常时一般捕获到的都是JS本身的不可开交,不用操心上诉问题。

小结

本章介绍了JS异步编程相关的文化,总结起来有以下几点:

  • 不掌握异步编程就非算是学会NodeJS。

  • 异步编程依托于回调来实现,而使用回调不自然就是是异步编程。

  • 异步编程下之函数间数传递、数组遍历和非常处理同协同编程有格外挺差距。

  • 使用domain模块简化异步代码的异常处理,并小心骗局。

大示例

学讲究的凡学以致用和通。至此我们都各自介绍了NodeJS的博知识点,本章作为最后一章,将完全地介绍一个用NodeJS开发Web服务器的以身作则。

需求

俺们而付出之凡一个概括的静态文件合并服务器,该服务器需要支持类似以下格式的JS或CSS文件合并请求。

http://assets.example.com/foo/??bar.js,baz.js

在以上URL中,??凡是一个分隔符,之前是急需联合之大半单文件之URL的官部分,之后是行使,隔的区别部分。因此服务器处理者URL时,返回的凡以下简单只文件按梯次合并后的内容。

/foo/bar.js
/foo/baz.js

此外,服务器也需能支撑类似以下格式的常见的JS或CSS文件要。

http://assets.example.com/foo/bar.js

如上就是举需求。

率先涂鸦迭代

迅速迭代是同等栽对的开发方式,因此我们在首先浅迭代时事先实现服务器的基本功能。

设计

简单分析了要求下,我们约会获取以下的设计方案。

           +---------+   +-----------+   +----------+
request -->|  parse  |-->|  combine  |-->|  output  |--> response
           +---------+   +-----------+   +----------+

也就是说,服务器会率先分析URL,得到请求的公文之路径和类别(MIME)。然后,服务器会读取请求的文件,并据顺序合并文件内容。最后,服务器返回响应,完成对平蹩脚呼吁的处理。

另外,服务器在读取文件时得出只根目录,并且服务器监听的HTTP端口最好为绝不写很于代码里,因此服务器需要是可部署的。

实现

冲上述设计,我们描绘起了第一本子代码如下。

var fs = require('fs'),
    path = require('path'),
    http = require('http');

var MIME = {
‘.css’: ‘text/css’,
‘.js’: ‘application/javascript’
};

function combineFiles(pathnames, callback) {
var output = [];

(function next(i, len) {
if (i < len) { fs.readFile(pathnames[i], function (err, data) { if (err) { callback(err); } else { output.push(data); next(i + 1, len); } }); } else { callback(null, Buffer.concat(output)); } }(0, pathnames.length)); }
function main(argv) {
var config = JSON.parse(fs.readFileSync(argv[0], ‘utf-8’)),
root = config.root || ‘.’,
port = config.port || 80;

http.createServer(function (request, response) {
var urlInfo = parseURL(root, request.url);

combineFiles(urlInfo.pathnames, function (err, data) {
if (err) {
response.writeHead(404);
response.end(err.message);
} else {
response.writeHead(200, {
‘Content-Type’: urlInfo.mime
});
response.end(data);
}
});
}).listen(port);
}

function parseURL(root, url) {
var base, pathnames, parts;

if (url.indexOf(‘??’) === -1) {
url = url.replace(‘/’, ‘/??’);
}

parts = url.split(‘??’);
base = parts[0];
pathnames = parts[1].split(‘,’).map(function (value) {
return path.join(root, base, value);
});

return {
mime: MIME[path.extname(pathnames[0])] || ‘text/plain’,
pathnames: pathnames
};
}

main(process.argv.slice(2));

如上代码完整兑现了服务器所需要的效能,并且有以下几点值得注意:

  1. 利用命令行参数传递JSON配置文件路径,入口函数负责读取配置并创办服务器。

  2. 入口函数完整描述了先后的运转逻辑,其中解析URL和合并文件之切实落实封装在其它少独函数里。

  3. 解析URL时优先拿日常URL转换为文件合并URL,使得个别种植URL的处理方式可以一样。

  4. 统一文件时采用异步API读取文件,避免服务器因为等磁盘IO而生围堵。

咱得以将上述代码保存也server.js,之后就足以经node server.js config.json一声令下启动程序,于是我们的首先版静态文件合并服务器即顺风完工了。

此外,以上代码有一个免那么泾渭分明的逻辑缺陷。例如,使用以下URL请求服务器时会起喜怒哀乐。

    http://assets.example.com/foo/bar.js,foo/baz.js

经分析下咱们见面发觉题目产生当/深受机关替换/??这作为及,而这个题材我们得以交第二次迭代时又解决。

其次破迭代

以首先不好迭代下,我们曾产生了一个只是工作之版本,满足了作用需求。接下来我们得从性质的角度出发,看看代码还有什么样改进余地。

设计

map术换成for巡回或许会再快有,但第一本子代码最老之属性问题存在于由读取文件及输出响应的历程中。我们盖拍卖/??a.js,b.js,c.js本条要为例,看看整个处理过程中耗时在何方。

 发送请求       等待服务端响应         接收响应
---------+----------------------+------------->
         --                                        解析请求
           ------                                  读取a.js
                 ------                            读取b.js
                       ------                      读取c.js
                             --                    合并数据
                               --                  输出响应

足见见,第一版本代码依次将要的公文读取到内存中之后,再统一数据及输出响应。这会促成以下简单只问题:

  1. 当求的文本较多较异常时,串行读取文件会较耗时,从而拉开了服务端响应等时。

  2. 由于每次响应输出的数目都亟待事先整体地复苏存在内存里,当服务器请求并发数较充分时,会发生较生之内存开销。

对此第一单问题,很易想到将读取文件的艺术从串行改呢彼此。但是变化这么做,因为对于机械磁盘而言,因为就发一个磁头,尝试并行读取文件就见面招磁头频繁抖动,反而降低IO效率。而于固态硬盘,虽然真正存在多只相IO通道,但是对服务器并行处理的大都只请求而言,硬盘已经以举行并行IO了,对单个请求采用互动IO无异于拆东墙补西墙。因此,正确的做法不是改用并行IO,而是一头读取文件一边输出响应,把响应输出时机提前至读取第一独公文之时刻。这样调整后,整个请求处理过程变成下边这样。

发送请求 等待服务端响应 接收响应
---------+----+------------------------------->
         --                                        解析请求
           --                                      检查文件是否存在
             --                                    输出响应头
               ------                              读取和输出a.js
                     ------                        读取和输出b.js
                           ------                  读取和输出c.js

遵循上述方式缓解第一单问题后,因为服务器无待总体地缓存每个请求的出口数据了,第二个问题吧解决。

实现

冲上述设计,第二版本代码按以下措施调动了片函数。

function main(argv) {
    var config = JSON.parse(fs.readFileSync(argv[0], 'utf-8')),
        root = config.root || '.',
        port = config.port || 80;

http.createServer(function (request, response) {
var urlInfo = parseURL(root, request.url);

validateFiles(urlInfo.pathnames, function (err, pathnames) {
if (err) {
response.writeHead(404);
response.end(err.message);
} else {
response.writeHead(200, {
‘Content-Type’: urlInfo.mime
});
outputFiles(pathnames, response);
}
});
}).listen(port);
}

function outputFiles(pathnames, writer) {
(function next(i, len) {
if (i < len) { var reader = fs.createReadStream(pathnames[i]);
reader.pipe(writer, { end: false });
reader.on(‘end’, function() {
next(i + 1, len);
});
} else {
writer.end();
}
}(0, pathnames.length));
}

function validateFiles(pathnames, callback) {
(function next(i, len) {
if (i < len) { fs.stat(pathnames[i], function (err, stats) { if (err) { callback(err); } else if (!stats.isFile()) { callback(new Error()); } else { next(i + 1, len); } }); } else { callback(null, pathnames); } }(0, pathnames.length)); }

得视,第二本代码在自我批评了请求的具备文件是否中之后,立即就输出了响应头,并跟着一边以梯次读取文件一边输出响应内容。并且,在读取文件时,第二本子代码直接采用了单独念数据流来简化代码。

其三次迭代

其次糟迭代从此,服务器本身的功能以及属性就收获了始于满足。接下来我们要从平安的角度再次审视一下代码,看看还索要做来什么。

设计

自打工程角度达讲,没有绝对可靠的体系。即使第二不成迭代的代码通过三番五次检讨后能管没有bug,也很难说是否会见坐NodeJS本身,或者是操作系统本身,甚至是硬件本身造成我们的服务器程序在某一样上挂掉。因此一般生环境下之服务器程序都放起一个医护进程,在劳动挂掉的上这又开服务。一般守护进程的代码会远较服务过程的代码简单,从概率上得以确保医护进程再麻烦挂掉。如果又做得小心一些,甚至守护进程本身可以当投机挂掉时再也开自己,从而实现双双管教。

于是当本次迭代时,我们先行下NodeJS的过程管理机制,将守护进程作为爸爸进程,将服务器程序作为子进程,并受大人进程监控子进程的运行状态,在该十分退出时再度启子进程。

实现

依据上述设计,我们编辑了医护进程需要之代码。

var cp = require('child_process');

var worker;

function spawn(server, config) {
worker = cp.spawn(‘node’, [ server, config ]);
worker.on(‘exit’, function (code) {
if (code !== 0) {
spawn(server, config);
}
});
}

function main(argv) {
spawn(‘server.js’, argv[0]);
process.on(‘SIGTERM’, function () {
worker.kill();
process.exit(0);
});
}

main(process.argv.slice(2));

另外,服务器代码本身的入口函数也使举行以下调。

function main(argv) {
    var config = JSON.parse(fs.readFileSync(argv[0], 'utf-8')),
        root = config.root || '.',
        port = config.port || 80,
        server;

server = http.createServer(function (request, response) {

}).listen(port);

process.on(‘SIGTERM’, function () {
server.close(function () {
process.exit(0);
});
});
}

我们得以拿守护进程的代码保存也daemon.js,之后咱们可以经过node daemon.js config.json启航服务,而护理进程会愈来愈启动与监察服务器进程。此外,为了能健康终止劳动,我们于医护进程在接到至SIGTERM信号时停服务器进程。而于服务器进程就同端,同样在吸纳SIGTERM信号时优先停止少HTTP服务又正常退出。至此,我们的服务器程序就凭借谱很多矣。

季涂鸦迭代

于咱们解决了服务器本身的效应、性能和可靠性的题目后,接着我们要考虑一下代码部署之题目,以及服务器控制的题材。

设计

诚如而言,程序在服务器上生一个恒定的布目录,每次程序来更新后,都再也发布到布置目录里。而如形成布置后,一般也可经稳定的服务控制脚本启动同止服务。因此我们的服务器程序部署目录可以开如下设计。

- deploy/
    - bin/
        startws.sh
        killws.sh
    + conf/
        config.json
    + lib/
        daemon.js
        server.js

当以上目录结构面临,我们分类存放了劳务控制脚本、配置文件及服务器代码。

实现

以上述目录结构分别寄存对应的文书从此,接下去我们省控制脚本怎么形容。首先是start.sh

#!/bin/sh
if [ ! -f "pid" ]
then
    node ../lib/daemon.js ../conf/config.json &
    echo $! > pid
fi

然后是killws.sh

#!/bin/sh
if [ -f "pid" ]
then
    kill $(tr -d '\r\n' < pid)
    rm pid
fi

于是这样咱们就是发出了一个简易的代码部署目录及劳务控制脚本,我们的服务器程序就可以上线工作了。

后续迭代

咱的服务器程序正式上线工作晚,我们连下去或者会发现还发出广大可改进的触发。比如服务器程序在集合JS文件时得自行在JS文件里插入一个;来避免有语法问题,比如服务器程序需要提供日志来统计访问量,比如服务器程序需要会充分利用多核CPU,等等。而此时底卿,在念了这样久NodeJS之后,应该就了解该怎么开了。

小结

本章将事先零散介绍的知识点串了起,完整地示范了一个以NodeJS开发顺序的例证,至此我们的课程就整个告终了。以下是针对性新出生之NodeJSer的组成部分提议。

  • 设若熟悉官方API文档。并无是说如果熟悉到能够记住每个API的称谓及用法,而是要熟悉NodeJS提供了什么职能,一旦用常明查询API文档的哪块地方。

  • 倘先期筹还落实。在出一个顺序前首先要生一个大局的计划性,不肯定要十分周全,但如若够能写起片代码。

  • 若是兑现后重新规划。在形容了一些代码,有矣部分切实的事物后,一定会意识部分前忽视掉的底细。这时还拨改进前的规划,为次车轮迭代做准备。

  • 倘若充分利用三方包。NodeJS有一个极大之生态圈,在描绘代码之前先瞧有无发出现成的老三正担保会节约成千上万工夫。

  • 毫无信三方包。任何业务做过度了就不好了,三正值担保吗是同等。三在担保是一个黑盒,每多采用一个老三正在担保,就为序增加了同一客机密风险。并且三正值担保大不便恰好就提供程序需要之法力,每多采取一个叔正在担保,就深受程序更为臃肿一些。因此于控制用有三正在担保之前,最好三思而后行。



 1 let common_url = 'http://192.168.1.1:8080/';  //服务器地址
 2 let token = '';   //用户登陆后返回的token
 3 /** 
 4  * 使用fetch实现图片上传
 5  * @param {string} url  接口地址
 6  * @param {JSON} params body的请求参数
 7  * @return 返回Promise 
 8  */
 9 function uploadImage(url,params){
10     return new Promise(function (resolve, reject) {
11         let formData = new FormData();
12         for (var key in params){
13             formData.append(key, params[key]);
14         }
15         let file = {uri: params.path, type: 'application/octet-stream', name: 'image.jpg'};
16         formData.append("file", file);
17         fetch(common_url + url, {
18             method: 'POST',
19             headers: {
20                 'Content-Type': 'multipart/form-data;charset=utf-8',
21                 "x-access-token": token,
22             },
23             body: formData,
24         }).then((response) => response.json())
25             .then((responseData)=> {
26                 console.log('uploadImage', responseData);
27                 resolve(responseData);
28             })
29             .catch((err)=> {
30                 console.log('err', err);
31                 reject(err);
32             });
33     });

行使办法

 1 let params = {
 2     userId:'abc12345',   //用户id
 3     path:'file:///storage/emulated/0/Pictures/image.jpg'    //本地文件地址
 4 }
 5 uploadImage('app/uploadFile',params )
 6     .then( res=>{
 7         //请求成功
 8         if(res.header.statusCode == 'success'){
 9             //这里设定服务器返回的header中statusCode为success时数据返回成功
10             upLoadImgUrl = res.body.imgurl;  //服务器返回的地址
11         }else{
12              //服务器返回异常,设定服务器返回的异常信息保存在 header.msgArray[0].desc
13             console.log(res.header.msgArray[0].desc);
14         }
15     }).catch( err=>{ 
16          //请求失败
17     })

注意点

1 let file = {uri: params.path, type: 'application/octet-stream', name: 'image.jpg'}中的type也可能是multipart/form-data
2 formData.append("file", file)中的的file字段也可能是images

普通网络请求参数是JSON对象 

图片上传的伸手参数使用的凡formData对象

总结:

React
Native中虽为搭了XMLHttpRequest
网络要API(也便是俗称之ajax),但XMLHttpRequest 是一个统筹粗糙的
API,不称职责分开的格,配置以及调用方式要命混乱,而且因事件之异步模型写起吧从未现代底
Promise 友好。而Fetch 的起就是为化解 XHR
的问题,所以react
Native官方推荐应用Fetch API。

fetch请求示例如下:

1 return fetch('http://facebook.github.io/react-native/movies.json')
2     .then((response) => response.json())
3     .then((responseJson) => {
4       return responseJson.movies;
5     })
6     .catch((error) => {
7       console.error(error);
8     });

使用Promise封装fetch请求

 1 let common_url = 'http://192.168.1.1:8080/';  //服务器地址
 2 let token = '';   
 3 /**
 4  * @param {string} url 接口地址
 5  * @param {string} method 请求方法:GET、POST,只能大写
 6  * @param {JSON} [params=''] body的请求参数,默认为空
 7  * @return 返回Promise
 8  */
 9 function fetchRequest(url, method, params = ''){
10     let header = {
11         "Content-Type": "application/json;charset=UTF-8",
12         "accesstoken":token  //用户登陆后返回的token,某些涉及用户数据的接口需要在header中加上token
13     };
14     console.log('request url:',url,params);  //打印请求参数
15     if(params == ''){   //如果网络请求中没有参数
16         return new Promise(function (resolve, reject) {
17             fetch(common_url + url, {
18                 method: method,
19                 headers: header
20             }).then((response) => response.json())
21                 .then((responseData) => {
22                     console.log('res:',url,responseData);  //网络请求成功返回的数据
23                     resolve(responseData);
24                 })
25                 .catch( (err) => {
26                     console.log('err:',url, err);     //网络请求失败返回的数据        
27                     reject(err);
28                 });
29         });
30     }else{   //如果网络请求中带有参数
31         return new Promise(function (resolve, reject) {
32             fetch(common_url + url, {
33                 method: method,
34                 headers: header,
35                 body:JSON.stringify(params)   //body参数,通常需要转换成字符串后服务器才能解析
36             }).then((response) => response.json())
37                 .then((responseData) => {
38                     console.log('res:',url, responseData);   //网络请求成功返回的数据
39                     resolve(responseData);
40                 })
41                 .catch( (err) => {
42                     console.log('err:',url, err);   //网络请求失败返回的数据  
43                     reject(err);
44                 });
45         });
46     }
47 }

style=”color: #ff6600;”>行使fetch请求,如果服务器返回的华语出现了乱码,则可以劳务器端设置如下代码解决:

produces="text/html;charset=UTF-8"

fetchRequest使用如下:
GET请求:
fetchRequest('app/book','GET')
    .then( res=>{
        //请求成功
        if(res.header.statusCode == 'success'){
            //这里设定服务器返回的header中statusCode为success时数据返回成功

}else{
//服务器返回异常,设定服务器返回的特别信息保存于 header.msgArray[0].desc
console.log(res.header.msgArray[0].desc);
}
}).catch( err=>{
//请求失败
})

POST请求:

let params = {
username:’admin’,
password:’123456′
}
fetchRequest(‘app/signin’,’POST’,params)
.then( res=>{
//请求成功
if(res.header.statusCode == ‘success’){
//这里设定服务器返回的header中statusCode为success时数返回成功

}else{
//服务器返回异常,设定服务器返回的要命信息保存在 header.msgArray[0].desc
console.log(res.header.msgArray[0].desc);
}
}).catch( err=>{
//请求失败
})

fetch超时处理

是因为原生的Fetch API
并无支持timeout属性,如果项目中得控制fetch请求的过期时间,可以针对fetch请求进一步封装实现timeout功能,代码如下:

fetchRequest超时处理封装

 1 /**
 2  * 让fetch也可以timeout
 3  *  timeout不是请求连接超时的含义,它表示请求的response时间,包括请求的连接、服务器处理及服务器响应回来的时间
 4  * fetch的timeout即使超时发生了,本次请求也不会被abort丢弃掉,它在后台仍然会发送到服务器端,只是本次请求的响应内容被丢弃而已
 5  * @param {Promise} fetch_promise    fetch请求返回的Promise
 6  * @param {number} [timeout=10000]   单位:毫秒,这里设置默认超时时间为10秒
 7  * @return 返回Promise
 8  */
 9 function timeout_fetch(fetch_promise,timeout = 10000) {
10     let timeout_fn = null; 
11 
12     //这是一个可以被reject的promise
13     let timeout_promise = new Promise(function(resolve, reject) {
14         timeout_fn = function() {
15             reject('timeout promise');
16         };
17     });
18 
19     //这里使用Promise.race,以最快 resolve 或 reject 的结果来传入后续绑定的回调
20     let abortable_promise = Promise.race([
21         fetch_promise,
22         timeout_promise
23     ]);
24 
25     setTimeout(function() {
26         timeout_fn();
27     }, timeout);
28 
29     return abortable_promise ;
30 }
31 
32 let common_url = 'http://192.168.1.1:8080/';  //服务器地址
33 let token = '';   
34 /**
35  * @param {string} url 接口地址
36  * @param {string} method 请求方法:GET、POST,只能大写
37  * @param {JSON} [params=''] body的请求参数,默认为空
38  * @return 返回Promise
39  */
40 function fetchRequest(url, method, params = ''){
41     let header = {
42         "Content-Type": "application/json;charset=UTF-8",
43         "accesstoken":token  //用户登陆后返回的token,某些涉及用户数据的接口需要在header中加上token
44     };
45     console.log('request url:',url,params);  //打印请求参数
46     if(params == ''){   //如果网络请求中没有参数
47         return new Promise(function (resolve, reject) {
48             timeout_fetch(fetch(common_url + url, {
49                 method: method,
50                 headers: header
51             })).then((response) => response.json())
52                 .then((responseData) => {
53                     console.log('res:',url,responseData);  //网络请求成功返回的数据
54                     resolve(responseData);
55                 })
56                 .catch( (err) => {
57                     console.log('err:',url, err);     //网络请求失败返回的数据        
58                     reject(err);
59                 });
60         });
61     }else{   //如果网络请求中带有参数
62         return new Promise(function (resolve, reject) {
63             timeout_fetch(fetch(common_url + url, {
64                 method: method,
65                 headers: header,
66                 body:JSON.stringify(params)   //body参数,通常需要转换成字符串后服务器才能解析
67             })).then((response) => response.json())
68                 .then((responseData) => {
69                     console.log('res:',url, responseData);   //网络请求成功返回的数据
70                     resolve(responseData);
71                 })
72                 .catch( (err) => {
73                     console.log('err:',url, err);   //网络请求失败返回的数据  
74                     reject(err);
75                 });
76         });
77     }
78 }