token
或者其他authorization
信息,响应时间,响应状态(http status)。解决方法:
请求响应日志
目前我们更多是在前面的 Nginx 那层去记录,这块要自己定制一个并不难,ctx.logger 那块应该可以覆盖掉默认的 format 的。request
- header
中,因公司不同,可能使用的全链路唯一标志不同。
有的公司用traceId
、而有的用request-id
,诸如此类的,如果都需要去改源码去完成,是否对生产的部署是一种障碍?
解决方法:
全链路标记可配置
这块属于 tracelog 范畴,这块其实跟企业内部的架构有关,需要去定制化的。我们内部有鹰眼系统,以及对应的插件,有兴趣可以跟进和推动下这个RFC记录的参数:请求方法,请求路由,请求参数(query,body;param收集的可能性不大?),作为一个api端需要的token
或者其他authorization
信息,响应时间,响应状态(http status)
request
- header
中,因公司不同,可能使用的全链路唯一标志不同。
有的公司用traceId
、而有的用request-id
,诸如此类的,如果都需要去改源码去完成,是否对生产的部署是一种障碍?
koa-log4
来复现已经成型的日志体系egg-logger
config.logger.level = 'NONE'
and config.logger.consoleLevel = 'NONE'
原生日志输出 cfork
一个第三方库输出到 stdout 的,这个肯定不好控制,但它并不会被写入到 file 里面的,不影响你分析。
全链路路由
尝试中间件进行修改日志格式:
它具有事件驱动,无阻塞,单线程等特性。
Node提供了net,dgram,http,https这4个模块,分别用于处理TCP,UDP,HTTP,HTTPS,适用于服务器端和客户端。
TCP传输控制协议
OSI:开放式系统互联通信参考模型。一种概念模型,由国际标准化组织提出,一个试图使各种计算机在世界范围内互连为网络的标准框架。
七层:
物理层(网络物理硬件),
数据链路层(网络特有的链路接口),
网络层(IP),
传输层(TCP/UDP),
会话层(通信连接/维持会话),
表示层(加密/解密等),
应用层(HTTP,SMTP,IMAP等)。
TCP:面向连接的协议,显著特征是传输之前需要3次握手形成会话。
在创建会话的过程中,服务器端和客户端分别提供一个套接字,这两个套接字共同形成一个连接。服务器端和客户端通过套接字实现两者之间连接的操作。
socket:套接字,使应用程序能够读写与收发通讯协定与资料的程序。
TCP创建过程和链接折除过程是由TCP/IP协议栈自动创建的。TCP的套接字是可写可读的stream对象。
TCP服务事件:
服务器事件,连接事件
TCP针对网路中的小数据报有一定的优化策略:Nagle算法.如果每次只发送一个字节的内容而不优化,网络中将充满只有少数有效数据的数据包,浪费网络资源。
Nagle算法针对这种情况,当缓冲区的数据达到一定数量或者一定时间后才将其发出,所以小数据宝将会被Nagle算法合并,以此来优化网络。这种优化虽然使网络带宽被有效的使用,但是数据可能被延迟发送。
一个套接字可以与多个UDP服务通信。
提供平面向事务的简单不可靠信息传输入伍,在网络差的情况下存在丢包严重的问题。无需链接,资源消耗低,处理快速灵活。
传输层http,smtp
HTTP:超文本传输协议
在websocket之前,网页客户端与服务器端进行通信最高效的是comet技术,实现comet技术的细节是采用长轮询或iframe流,长轮询的原理是客户端向服务端发起请求,服务端只在超时或有数据相应时断开连接,客户端在收到数据或者超时后重新发起请求,这个请求行为拖长长的尾巴。
凭借事件驱动和V8高性能,成为服务端额佼佼者。
无需切换语言环境,部分知识不会因为语言环境的切换而丢失,上下文一致性好。
数据(因为JSON)可以很好地实现跨前后端直接使用。
业务,可以轻量的选择在前端还是在后端进行,语言相通,代价小。
请求方法的判断,URL的路径解析,Cookie的解析,Basic认证,表单数据的解析,任意格式文件的上传处理。
BASIC认证:一种用来允许网页浏览器或其他客户端程序在请求时提供用户名和口令形式的身份凭证的一种登录验证方式
高阶函数的介绍:可能无限的复杂,但是只要对总结果返回一个上面的函数作为参数,传递给CreateServer()
侦听器就好了
RESTful
类web服务中请求的方法十分重要,因为他会决定资源的操作行为。PUT代表一个新建一个资源,POST表示更新一个资源,GET表示查看一个资源,DELETE表示删除一个资源。
性能的影响:一旦服务器向客户端附送了设置Cookie的意图,除非Cookie过期,否则客户端每次请求都会发送这些cookie到服务端,cookie过多会导致报头较大。
YSlow性能规则:
减小Cookie的大小
为静态组件使用不同的域名,将域名转换IP需要进行DNS查询,多一个域名就会多一次DNS查询
减少DNS查询
如何将每一个客户和服务器中的数据一一对应起来:
基于cookie来实现用户和数据的映射
服务器开启了session,将约定一个键值作为session的口令,服务器没有检查到用户请求cookie中携带,将会生成一个值唯一不重复,并设置超时时间。
通过查询字符串来实现浏览器和服务器端数据的对应
风险:因为要将地址栏中的地址发送给另外一个人,那么他就和你相同的身份。cookie的方案在换了浏览器或者电脑之后无法生效,较为安全。
Node与缓存服务保持长链接,而非频繁的短连接,握手导致的延迟只影响初始化。
高速缓存直接在内存中进行数据存储和访问。
缓存服务通常与Node进程运行在相同的机器上或者相同的机房里,网络速度受到的影响较小。
session需要异步的方式获取。
将值通过私钥签名:
var sign=function(val,secret){
return val+'.'+crypto.createHmac('sha256'.secret).update(val).digest('base64').replace(/\=+$/,'');
};
添加expire或cache-control到报头中
配置ETags
让ajax可缓存
检测本地资源是否可用会进行条件请求,在普通的GET请求报文中附带If-Modified-Since字段。
如果服务器没有新的版本,响应一个304的状态码,客户端使用本地的版本。
如果服务器有新的版本,将新的内容发送给客户端,客户端放弃本地版本。
// 检测本地资源是否可用
var handle = function(req, res) {
fs.stat(filename, function(err, stat) {
var lastModified = stat.mtime.toUTCString();
if (lastModified === req.headers['if-modified-since']) {
res.writeHead(304, 'Not Modified');
res.end();
} else {
fs.readFile(filename, function(err, file) {
var lastModified = stat.mtime.toUTCString();
res.setHeader('Last-Modified', lastModified);
res.writeHead(200, 'OK');
res.end();
});
}
});
};
文件的时间戳改动但内容不一定改动。
时间戳只能精确到秒,更新频繁的内容将无法生效。
HTTP/1.1 ETag来解决。
每次发布,路径中跟随web应用的版本号。
每次发布,路径中跟随该文件内容的Hash值。
res.setHeader('WWW-Authenticate','Basic realm="Secure Area"');
var hasBody=function(req){
return 'transfer-encoding' in req.headers || 'content-length' in req.headers;
}
// 流的方式处理data
function(req, res) {
if (hasBody(req)) {
var buffers = [];
req.on('data', function() {
buffers.push(chunk);
});
req.on('end', function() {
req.rawBody = Buffer.concat(buffers).toString();
handle(req, res);
});
} else {
handle(req, res);
}
};
判断content-type:
var mime=function(req){
var str=req.headers['content-type']||'';
return str.split(';')[0];
}
解析xml文件:
var xml2js=require('xml2js');
var handle = function(req,res){
if(mime(req)==='application/xml'){
xml2js.parseString(req.rawBody,function(err,xml){
if(err){
res.writeHead(400);
res.end('Invalid xml');
return;
}
req.body=xml;
todo(req,res);
})
}
}
接收大小未知的数据量的时候:
function (req,res){
if(hasBody(req)){
var done=function(){
handle(req,res);
}
if(mime(req)==='application/json'){
parseJSON(req,done);
}else if(mime(req)==='application/xml'){
parseXML(req,done);
}else if(mime(req)==='multipart/form-data'){
parseMulipart(req,done);
}
}else{
handle(req,res);
}
}
将req
这个流对象直接交给对应的解析方法,有解析方法自行处理上传的内容,或接受内容保存在内存中,或流式处理掉。
formiable
基于流式理解解析报文,将接收到的文件写入到系统的临时文件夹,并返回对应的路径
// formidable 基于流式处理解析报文
var formidable = require('formidable');
var upload = function (req, res) {
if (hasBody(req)) {
if (MimeType(req) == 'mulipart/form-data') {
var form = new formidable.IncomingForm();
form.parse(req, function (err, fields, files) {
req.body = fields;
req.files = files;
handle(req, res);
});
}
} else {
handle(req, res);
}
}
限制上传内容的大小,一旦超过限制就停止接受数据,并相应400状态码。
通过流式解析,将数据流导向磁盘中,Node只保留文件路径等小数据。
// 限制文件的大小
var bytes = 1024;
var limit = function (req, res) {
var received = 0;
var len = req.headers['content-length'] ? parseInt(req.headers['content-length'], 10) : null;
if (len && len > bytes) {
res.writeHead(413);
res.end();
return;
}
//limit
req.on('data', function (chunk) {
received += chunk.length;
if (received > bytes) {
req.destory();
}
});
handle(req, res);
}
对于没有content-length
的请求报文,在每个data事件中判断即可,but,json文件和XML文件可能无法完成解析。
// 检测
var validateCSRF = function (req, res) {
var token = req.session._csrf || (req.session._csrf = generateRandom(24));
var _csrf = req.body._csrf;
if (token !== _csrf) {
res.writeHead(403);
res.end('禁止访问');
} else {
handle(req, res);
}
}
// csrf添加
var generateRandom = function (len) {
return crypto.randomBytes(Math.ceil(len * 3 / 4)).toString('base64').slice(0, len);
}
MVC模型的主要思想是将业务逻辑按职责分离
控制器
模型
视图
工作模式:
路由解析,行为调用相关的模型进行数据操作,数据操作结束后调用视图和相关数据进行页面的渲染,输出到客户端。