Node基本
- node的最大特性莫过于基于事件驱动的非阻塞I/O模型。
- node通过事件驱动的方式处理请求,无须为每一个请求创建额外的对应线程,可以省掉创建线程和销毁线程的开销,同时操作系统在调度任务时因为线程较少,上下文切换的代价很低。这使得服务器能够有条不紊地处理请求,即使在大量连接的情况下,也不受线程上下文切换开销的影响,这是Node高性能的一个原因。Apache会为每个请求启动一个线程,每个线程会占用一定内存,并发量较大时会缓慢。Nginx也使用和node一样的事件驱动方式处理请求。
- node对引入过的模块都会进行缓存,以减少二次引入时的开销,node缓存的是编译和执行后的对象。不论是核心模块还是文件模块,require( )方法对相同模块的二次加载都一律采用缓存优先的方式,这是第一优先级的。不同之处在于核心模块的缓存检查先于文件模块的缓存检查。
- 事件轮询:从本质上来说,node会先注册事件,随后不停地询问内核这些时间是否已经分发。当事件分发时,对应的回调函数就会被触发,然后继续执行下去。如果没有事件触发,则继续执行其他代码,直到有新事件时,再去执行对应的回调函数。
- 在程序中定义配置项如app.set(“photo”,__dirname+’/public/photos’)后就可以在各种环境下任意改变此目录。
- 当node接收到从浏览器发来的http请求时,底层的TCP连接会分配一个文件描述符。随后,如果客户端向服务器发送数据,node就会收到该文件描述符上的通知,然后触发JavaScript的回调函数。
- 对于阻塞式I/O线程在执行中遇到磁盘读写或者数据库通讯,网络通讯这种耗时比较多的时候,操作系统将会剥夺此线程的CPU资源,并暂停此线程,转而去执行别的线程。异步式I/O则针对所有操作采取不阻塞的方法,当线程遇到I/O操作时将操作发送给操作系统,然后接着执行下一个操作,当操作系统执行完I/O操作之后,将以事件的方式通知执行I/O的线程,线程会在特定的时候执行这个事件。这一切的前提就是系统需要一个时间循环,以不断地去查询有没有未处理的事件,然后交给预处理。对比与阻塞I/O,异步模型极大提高了web服务器的并发性。
- buffer是一个表示固定内存分配的全局对象(即放到缓冲区的字节数需要提前确定)
- node的HTTP服务器是构建于node TCP服务器之上的,即node中的HTTP.server继承自net.server(net为TCP模块)。TCP的首要特性是它面向连接。
- Node中的管道可以让数据流动到指定目的地(即WriteableStream)读取一个文件并写入到另一个文件中,利用pipe()作为连接,ReadableStream.pipe(WriteableStream).HTTP请求中在客户端request就是一个WriteableStream,response为Readablestream,而在服务器端则相反。
- node与操作系统交互的工具
- 全局的process对象————包含当前进程的相关信息,如传参和当前设定的环境变量
- fs模块————包含底层的ReadStream和WriteStream类。
- child_process模块————繁衍子进程的底层和高层接口,以及一种繁衍带有双向信息传递通道node实例的特殊办法。
- node内置了调试器,控制台输入node debug app.js可以分步调试程序,n(下一步)、s(步入)、o(步出)。n会跨过当前行:调试器会执行这一行,但如果指令调用了其他函数,在这些函数执行完后才把控制权交还。s与之不同,如果调用其他函数会进入函数内部逐步执行。o允许跳出当前正在执行的函数。
- 也可以使用node探查器node-inspector,可以在浏览器内逐步调试。
node的异步I/O
事件循环是异步实现的核心,它与浏览器中的执行模型基本保持一致。
在启动node进程时会创建一个类似while(true)的循环,每执行一次循环体的过程我们称之为Tick。每个Tick的过程就是查看是否有事件待处理,如果有,就取出事件及其相关的回调函数。如果存在关联的回调函数,就执行他们。然后进入下一个循环,如果不再有事件处理,就退出进程。
事件循环是一个典型的生产者/消费者模型。异步I/O、网络请求等则是事件的生产者,源源不断为Node提供不同类型的事件,这些事件被传递到对应的观察者那里,事件循环则从观察者那里取出事件并处理。
-
事实上,在node中,除了javascript是单线程的,Node本身其实是多线程的,只是I/O线程使用的CPU较少。除了用户代码无法并行执行外,所有的I/O(磁盘I/O和网络I/O等)是可以并行起来的。
非I/O的api setTimeout和setInterval
- 定时器的问题在于它并非精准的(在容忍范围内)。如果一次循环占用的时间过多,那么下次循环时可能会超时。如通过setTimeout设置一个任务在10毫秒之后执行,但是在9毫秒后,有个任务占用了5毫秒的CPU时间片,再次轮到定时器执行时,时间就已经过期四毫秒。
Buffer对象
Buffer对象类似于数组,它的元素为16进制的两位数,即0~255的数值。
Bufffer可以对字符串的编码进行转换,通过字符串创建一个buffer:new Buffer(str,[encoding]);若encoding不传值默认utf8编码。
buffer转出字符串,利用buffer.tostring([encoding],[start],[end]);
在传输数据时经常使用trunk来表示表示buffer数据,data+=trunk;等价于data=data.tostring()+trunk.tostring();这个适用于传输内容为英文的数据,中文uft8编码中占三个字节,如果中间隔开会出现乱码。
如果是socket通信则可以使用socket.setEncoding([encoding])来设置接受数据的编码格式。
-
为了不乱码,buffer的正确拼接方式是用一个数组来存储接收所有Buffer片段并记录下所有片段的总长度,然后调用数组的Buffer.concat()方法生成一个合并的buffer对象
var chunks = [];
var size = 0;
res.on('data', function (chunk) {
chunks.push(chunk);
size += chunk.length;
});
res.on('end', function () {
var data = null;
switch(chunks.length) {
case 0: data = new Buffer(0);
break;
case 1: data = chunks[0];
break;
default:
data = new Buffer(size);
for (var i = 0, pos = 0, l = chunks.length; i < l; i++) {
var chunk = chunks[i];
chunk.copy(data, pos);
pos += chunk.length;
}
break;
}
});
```
函数式编程
高阶函数
- 高阶函数可以将函数作为输入或者返回值,事件的回调处理是基于高阶函数的特性来完成的。高阶函数可以让事件十分方便地进行复杂业务逻辑的解耦。
- 偏函数用法是指创建一个调用另外一个部分——参数或变量已经预制的函数——的函数的用法。实现方法类似于工厂模式通过指定部分参数来产生一个新的定制函数。
偏函数
偏函数你可以理解为工厂函数,也就是这种函数是用来当做模板来生产出函数的,因为作为一等公民,函数可以作为返回值。
-
一个javascript对类型的判断的例子:
var toString = Object.prototype.toString; var isString = function (obj){ return toString.call(obj) == '[object String]'; }; var isFunction = function(obj){ return toString.call(obj) == '[object Function]'; };
网络通信
REST API
API与用户的通信协议,总是使用HTTPs协议。
应该尽量将API部署在专用域名之下,或者主域名下:https://example.org/api/
应该将API的版本号放入URL:https://api.example.com/v1/
网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。
-
对于资源的具体操作类型,由HTTP动词表示。常用的HTTP动词有下面五个(括号里是对应的SQL命令)。
GET(SELECT):从服务器取出资源(一项或多项) POST(CREATE):在服务器新建一个资源 PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源) PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性) DELETE(DELETE):从服务器删除资源
-
如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。
下面是一些常见的参数。?limit=10:指定返回记录的数量 ?offset=10:指定返回记录的开始位置。 ?page=2&per_page=100:指定第几页,以及每页的记录数。 ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。 ?animal_type_id=1:指定筛选条件
-
服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)
200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务) 204 NO CONTENT - [DELETE]:用户删除数据成功。 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
-
针对不同操作,服务器向用户返回的结果应该符合以下规范。
GET /collection:返回资源对象的列表(数组) GET /collection/resource:返回单个资源对象 POST /collection:返回新生成的资源对象 PUT /collection/resource:返回完整的资源对象 PATCH /collection/resource:返回完整的资源对象 DELETE /collection/resource:返回一个空文档
RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
node提供net、dgram、http、https模块来创建TCP、UDP、HTTP、HTTPS服务器。
webSocket
- 浏览器通过在请求头中添加Upgrade:webSockt和Connection:Upgrade来表示请求服务器端升级协议为websockt。通常使用Sec-WebSockt-Key用于安全校验。
HTTPS
- SSL作为一种安全协议,在传输层提供对网络连接加密的功能,对应用层而言是透明的,数据在传递到应用层之前就已经完成了加密和解密的过程。
- node在网络安全方面提供了三个模块:
- crypto主要用于加密解密,SHA1和MD5等算法。
- tls提供了与net模块类似的功能,区别在于建立的是TLS/SSL加密的TCP连接上。
- https与http模块接口一致,区别在于建立安全的连接。
Node使用
在node中定义了静态文件app.use(express.static(path))之后如果要用里面数据可以直接使用以/开头的静态文件相对路径。
node删除file使用fs.unlink( )!
node中fs.stat( )系统调用获取文件的相关信息,比如修改时间、字节数等,如果文件不存在fs.stat()会在err.code中放入ENOOENT作为响应。
app.use('/api',api.auth)这是挂载点,即任何以/api开头的请求路径名和HTTP谓词都会导致这个中间件被调用。
-
node中引入crypto模块用于加密
function sha1(str){
var md5sum = crypto.createHash("sha1");
md5sum.update(str);
str = md5sum.digest("hex");
return str;
}
```
模块内引用路径时一定要使用绝对路径,借助于全局变量__dirname与__filename来使用。因为当主函数使用模块时若是相对路径则以主函数作为参考。
标准库组件url可以补全地址url.resolve(主站url,href)。
node中的全局函数process.argv为一个argument的数组索引为零的储存的是node所在文件夹,索引为1的是当前执行文件路径,后面的索引为在命令行启动node时传入的参数。
模块导出时用module.exports={函数名}的形式,导入模块时即使在同一目录下也需要在开头加“./文件名.js”若不加./则优先在内置模块中寻找,然后是在node_modules文件夹中寻找。
绝大部分node异步API接收的回调函数,第一个参数都是错误对象或者是null。
-
Event模块时node对‘发布/订阅’模式的实现,其中提供了一个EventEmitter对象,核心事件就是事件的触发与事件监听功能的封装。
var EventEmitter = require('events').EventEmitter;
var emitter = new EventEmitter; //初始化对象
emitter.on(‘自定义事件名’,function(){}) //绑定事件
emitter.emit(‘事件名’,‘回调函数的参数’) // 触发事件
```
- 使用net.creatServer( function(socket){})时候回调函数传回来的是一个socket对象,可以通过给socket对象setEncoding来设置接收到的流的显示方式。socket.on(‘data’,回调函数)来给socket绑定接受到数据时触发的事件。
- fs.createReadStream('fire.jpg').pipe(respond);在http中通过流的方式给页面中传图片。
- node中http会将所有主机名后面的内容放入request.url中.
- node提供querystring的模块,该模块含有一个.parse( )方法,传入参数如“q=1”的字符串返回一个{q:1}的对象。这个解析处理方式和Node解析header消息的方法类似,node将http请求数据中的header信息从字符串解析成一个方便处理的header对象。同样可以使用querystring.stringfy({q:1})将对象转为字符串。
- 在http的请求中一般通过给respond绑定data事件来获取返回的数据,绑定end事件等待数据获取完后执行相应操作。
- node在设置登录页面时,如果用到了第三方认证如微博QQ等登录可以使用passport模块,方便配置各种类型的OAuth。
- express客户端使用socket.io要引入socket.io给客户端写的js文件<script src='/socket.io/socket.io.js'></script>
- express中使用socket.io模块,如果要群发信息则需要每个客户端都加入到同一房间内,使用socket.join(‘房间名’);来加入房间,然后使用socket.to(‘房间名’).emit(‘event’,fn)来广播消息,消息默认只对除当前客户端之外的所有客户端发送,如果还要对当前客户端发送则再加上socket.emit(‘event’,fn)。可以利用socket.rooms来查看客户端所在的房间,返回一个对象。一个客户端可以在多个房间,第一个房间与客户端的id相同,是一个随机的字符串,如果要针对某个客户端单独发消息可以用to指向客户端所对应的专属房间。
- express中路由和中间件的添加顺序至关重要,如果把404处理器放在所有路由上面,首页和相关页面就无法显示。自己编写中间件时如果方法后面没有调用next()则不会再执行之后的中间件。使用app.use(‘/url’,中间件),第一个参数表示当url前缀与之匹配是才会调用后面的中间件。
- bodyParser()组件用于解析POST请求,它提供了req.body属性,可以用来解析注册信息如JSON、x-www-form-urlencoded(HTML表单的默认值)和multipart/form-data请求。如果是multipart/form-data请求,如文件上传,则还有req.files对象。
- query()组件主要用于解析GET请求,它提供req.query对象将url中的GET数据转化成一个对象存在req.query中。
- cheerio组件为一个 Node.js 版的 jquery,用来从网页中以 css selector 取数据,使用方式跟 jquery 一样一样的。在爬虫应用中使用superagent.get()得到网页的数据srea之后利用cheerio 来将数据解析var $ = cheerio.load(sres.text);之后通过$(‘.’)方式来获取DOM。
- 一定要注意superagent使用的是异步请求,会在发送去请求后继续执行下面代码而非在获得请求结果后。
- 利用eventproxy组件针对多个异步请求统一处理回调函数,无深度嵌套。用来检测多个异步操作是否完成,完成之后,会自动调用处理函数,并将抓取到的数据当参数传过来。
- 当你需要去多个源(一般是小于 10 个)汇总数据的时候,用 eventproxy 方便;当你需要用到队列,需要控制并发数,或者你喜欢函数式编程思维时,使用 async做异步处理。
- nodemon这个库是专门调试时候使用的,它会自动检测node.js 代码的改动,然后帮你自动重启应用。在调试时可以完全用 nodemon 命令代替 node 命令。$ nodemon app.js 启动应用。
- 上传大型文件时可以使用formidable的流式解析器,它可以随着数据块的上传接收它们并呈现特定的部分这种方式不仅快,还不会因为需要大量的缓冲而导致内存膨胀,即便像视频这种大型文件,也不会把进程压垮。
- 管理用户密码文件时所谓加Salt,就是加点“佐料”。当用户首次提供密码时(通常是注册时),由系统自动往这个密码里加一些“Salt值”,这个值是由系统随机生成的,并且只有系统知道。然后再散列。而当用户登录时,系统为用户提供的代码撒上同样的“Salt值”,然后散列,再比较散列值,已确定密码是否正确。这样,即便两个用户使用了同一个密码,由于系统为它们生成的salt值不同,他们的散列值也是不同的。即便黑客可以通过自己的密码和自己生成的散列值来找具有特定密码的用户,但这个几率太小了(密码和salt值都得和黑客使用的一样才行)。参看express的auth示例。
- Post/Redirect/Get(PRG)模式是一个常用的web程序设计模式。在这种模式中,用户请求表单,用HTTP/POST请求表单数据,然后用户被重定向到另外一个web页面上。被重定向到哪里取决于表单数据是否有效。如果表单数据无效,程序会让用户回到表单页面。如果表单数据有效,程序会让用户到新的页面中。PRG模式主要是为了防止表单的重复提交。
- 在express中用户被重定向后,res.locals中的内容会被重置。Server传来消息最好存在会话变量中。res.message函数可以吧消息添加到任何Express请求的会话变量中。
- express.response对象是Express给相应对象的原型,向这个对象中添加属性意味着所有的中间件和路由都能访问它们。
- 使用res.redirect()时第一个参数可以填HTTP的Status Code,301重定向是“永久”的,意味着浏览器会缓存重定向目标。如果使用301重定向并试图第二次提交表单,浏览器会绕过整个处理程序并直接进入相应页面。使用303重定向不会缓存重定向目标。默认是302重定向。
- http中结束一次会话后记得调用res.end()结束请求,否则客户端将一直处于等待状态。
Async库
Async库是为了处理nodejs中的异步任务,同样也可以设置任务的同步执行流程。
使用async调用的函数必须是有一个callback参数,并在函数执行完后调用callback(null, "done!");第一个参数是error参数。如果任何一个函数向它的回调函数中传了一个error,则后面的函数都不会被执行,并且将会立刻会将该error以及已经执行了的函数的结果,传给series中最后那个callback。
- series(tasks, [callback]) (多个函数依次执行,之间没有数据交换)
- waterfall(tasks, [callback]) (多个函数依次执行,且前一个的输出为后一个的输入)
- parallel(tasks, [callback]) (多个函数并行执行)
- auto(tasks, [callback]) (多个函数有依赖关系,有的并行执行,有的依次执行)
- whilst(test, fn, callback)(用可于异步调用的while)
BasicAuth库
- basicAuth中间件为网站添加身份认证功能,使用该中间件后,用户访问网站必须输入用户名和密码并通过难后才能访问网站。只提供最基本的访问授权,并且只能通过HTTPS使用。只有在需要又快又容易的东西,并且在HTTPS时才用到BasicAuth。
路由参数
app.get(/staff/:name,function(){})
node中路由系统会将参数值放入req.params.name中。
ejs模板
- 使用render来渲染输出模板:ejs.render(str, data, options); // render的返回值是最终结果string
- ejs中循环输出
<ul>
<% names.forEach(function(name){ %>
<li><%= name %></li>
<% }) %>
</ul>
options
```
cache:true, //是否对结果进行缓存(需要filename)
filename:"path", //cache的key,用于include指令中
scope:"this", //指定函数执行的上下文对象
debug:true, //输出生成的函数体
compileDebug:false, //为false时,debug指令不会被编译
client:"", //返回独立的编译后的函数
open:"<%", //指定开标签
close:"%>", //指定闭标签
```
如果定义了一段ejs模板作为公共模板,现在想引用这段公共模板的话,可以用include指令,需要在options里指定filename和传入参数:include('user/show', {user: user})
-
过滤器:用于将结果进一步加工的API:
<p><%=: users | map:'name' | join %></p> //注意起始的:和中间的|标记
ejs.render(str, {
users: [
{ name: 'tj' },
{ name: 'mape' },
{ name: 'guillermo' }
]
});
// 结果是:<p>Tj, Mape, Guillermo</p>
child_process模块中的spawn和exec方法
这两个方法都可以被用来开启一个子进程来执行其他的程序.
child_process.spaen会返回一个带有stdout和stderr流的对象。你可以通过stdout流来读取子进程返回给Node.js的数据。stdout拥有’data’,’end’以及一般流所具有的事件。当你想要子进程返回大量数据给Node时,比如说图像处理,读取二进制数据等等,你最好使用spawn方法。
child_process.spawn方法是“异步中的异步”,意思是在子进程开始执行时,它就开始从一个流总将数据从子进程返回给Node。
-
比如说我们想从一个URL下载文件,我们选择使用curl工具,此时,我们就可以在Node中使用spawn运行curl工具,下面是具体代码:
var file = fs.createWriteStream(DOWNLOAD_DIR + file_name); var curl = spawn('curl', [file_url]); // 为spawn实例添加了一个data事件 curl.stdout.on('data', function(data) { file.write(data); }); // 添加一个end监听器来关闭文件流 curl.stdout.on('end', function(data) { file.end(); console.log(file_name + ' downloaded to ' + DOWNLOAD_DIR); }); // 当子进程退出时,检查是否有错误,同时关闭文件流 curl.on('exit', function(code) { if (code != 0) { console.log('Failed: ' + code); } });
child_process.exec方法会从子进程中返回一个完整的buffer。默认情况下,这个buffer的大小应该是200k。如果子进程返回的数据大小超过了200k,程序将会崩溃,同时显示错误信息“Error:maxBuffer exceeded”。
-
child_process.exec方法是“同步中的异步”,尽管exec是异步的,它一定要等到子进程运行结束以后然后一次性返回所有的buffer数据。如果exec的buffer体积设置的不够大,它将会以一个“maxBuffer exceeded”错误失败告终。
var child = exec(cmomand, function(err, stdout, stderr) { if (err) throw err; else console.log(file_name + ' downloaded to ' + DOWNLOAD_DIR); });
爬虫
- __VIEWSTATE
a) ViewState是ASP.NET中用来保存WEB控件回传时状态值一种机制。在WEB窗体(FORM)的设置为runat="server",这个窗体(FORM)会被附加一个隐藏的属性_VIEWSTATE。_VIEWSTATE中存放了所有控件在ViewState中的状态值。 ViewState是类Control中的一个域,其他所有控件通过继承Control来获得了ViewState功能。它的类型是system.Web.UI.StateBag,一个名称/值的对象集合。
b) 当请求某个页面时,ASP.NET把所有控件的状态序列化成一个字符串,然后做为窗体的隐藏属性送到客户端。当客户端把页面回传时,ASP.NET分析回传的窗体属性,并赋给控件对应的值。 - __EVENTVALIDATION
__EVENTVALIDATION只是用来验证事件是否从合法的页面发送,只是一个数字签名,所以一般很短。“id”属性为“__EVENTVALIDATION”的隐藏字段是ASP.NET 2.0的新增的安全措施。该功能可以阻止由潜在的恶意用户从浏览器端发送的未经授权的请求.
为了确保每个回发和回调事件来自于所期望的用户界面元素,ASP.NET运行库将在事件中添加额外的验证层。服务器端通过检验表单提交请求的内容,将其与“id”属性为“__EVENTVALIDATION”隐藏字段中的信息进行匹配。根据匹配结果来验证未在浏览器端添加额外的输入字段(有可能为用户在浏览器端恶意添加的字段),并且该值是在服务器已知的列表中选择的。ASP.NET运行库将在生成期间创建事件验证字段,而这是最不可能获取该信息的时刻。像视图状态一样,事件验证字段包含散列值以防止发生浏览器端篡改。
说明:“id”属性为“__EVENTVALIDATION”隐藏字段一般在表单的最下方,如果表单在浏览器端尚未解析完毕时,用户提交数据有可能导致验证失败。__EVENTVALIDATION与__VIEWSTATE一般可以在浏览器页面源代码中找到,作为form中被隐藏的标签,与其他数据一同提交。 - 使用superagent得到response的cookie: JSON.stringify(res.header["set-cookie"]);
- 针对superagent获取的网页乱码的情况,superagent-charset扩展了superagent的功能,使其可以手动指定编码功能。
- MD5加密是一种不可逆加密算法,使用MD5加密时相同内容以字符串传入与数字传入得到的结果不同。