日志简介和构建web应用

Logger 日志规范

请求响应日志:

  • 需要记录的参数:请求方法,请求路由,请求参数。作为一个api端需要的token或者其他authorization信息,响应时间,响应状态(http status)。
  • 记录日志的格式自定义

解决方法:

  • 请求响应日志 目前我们更多是在前面的 Nginx 那层去记录,这块要自己定制一个并不难,ctx.logger 那块应该可以覆盖掉默认的 format 的。

日志可封装可解析:

  • 直接引入egg-logger后,原生的日志输出不符合基本需求,并且格式不统一。

全链路可配置:

request - header中,因公司不同,可能使用的全链路唯一标志不同。
有的公司用traceId、而有的用request-id,诸如此类的,如果都需要去改源码去完成,是否对生产的部署是一种障碍?

解决方法:

  • 全链路标记可配置 这块属于 tracelog 范畴,这块其实跟企业内部的架构有关,需要去定制化的。我们内部有鹰眼系统,以及对应的插件,有兴趣可以跟进和推动下这个RFC

优雅的使用egg-logger日志

  • 请求日志的格式

记录的参数:请求方法,请求路由,请求参数(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网络编程

它具有事件驱动,无阻塞,单线程等特性。

Node提供了net,dgram,http,https这4个模块,分别用于处理TCP,UDP,HTTP,HTTPS,适用于服务器端和客户端。

构建TCP应用(传输控制协议)

TCP传输控制协议

OSI:开放式系统互联通信参考模型。一种概念模型,由国际标准化组织提出,一个试图使各种计算机在世界范围内互连为网络的标准框架。

七层:

物理层(网络物理硬件),

数据链路层(网络特有的链路接口),

网络层(IP),

传输层(TCP/UDP)

会话层(通信连接/维持会话),

表示层(加密/解密等),

应用层(HTTP,SMTP,IMAP等)。

TCP:面向连接的协议,显著特征是传输之前需要3次握手形成会话。

在创建会话的过程中,服务器端和客户端分别提供一个套接字,这两个套接字共同形成一个连接。服务器端和客户端通过套接字实现两者之间连接的操作。

socket:套接字,使应用程序能够读写与收发通讯协定与资料的程序。

TCP创建过程和链接折除过程是由TCP/IP协议栈自动创建的。TCP的套接字是可写可读的stream对象。

TCP服务事件:

服务器事件,连接事件

TCP针对网路中的小数据报有一定的优化策略:Nagle算法.如果每次只发送一个字节的内容而不优化,网络中将充满只有少数有效数据的数据包,浪费网络资源。

Nagle算法针对这种情况,当缓冲区的数据达到一定数量或者一定时间后才将其发出,所以小数据宝将会被Nagle算法合并,以此来优化网络。这种优化虽然使网络带宽被有效的使用,但是数据可能被延迟发送。

构建UDP服务 (用户数据包协议)

一个套接字可以与多个UDP服务通信。

提供平面向事务的简单不可靠信息传输入伍,在网络差的情况下存在丢包严重的问题。无需链接,资源消耗低,处理快速灵活。

构建HTTP服务

传输层http,smtp

HTTP:超文本传输协议

构建websocket服务

在websocket之前,网页客户端与服务器端进行通信最高效的是comet技术,实现comet技术的细节是采用长轮询或iframe流,长轮询的原理是客户端向服务端发起请求,服务端只在超时或有数据相应时断开连接,客户端在收到数据或者超时后重新发起请求,这个请求行为拖长长的尾巴。

构建web应用

凭借事件驱动和V8高性能,成为服务端额佼佼者。

  • 前后端都是JS的时候,在跨越HTTP进行沟通时:

无需切换语言环境,部分知识不会因为语言环境的切换而丢失,上下文一致性好。

数据(因为JSON)可以很好地实现跨前后端直接使用。

业务,可以轻量的选择在前端还是在后端进行,语言相通,代价小。

  • request事件发生于网络连接
Created with Raphaël 2.2.0 开始 客户端发送报文 服务端解析报文 结束
  • 具体业务的需求

请求方法的判断,URL的路径解析,Cookie的解析,Basic认证,表单数据的解析,任意格式文件的上传处理。

BASIC认证:一种用来允许网页浏览器或其他客户端程序在请求时提供用户名和口令形式的身份凭证的一种登录验证方式

高阶函数的介绍:可能无限的复杂,但是只要对总结果返回一个上面的函数作为参数,传递给CreateServer()侦听器就好了

  • RESTful类web服务中请求的方法十分重要,因为他会决定资源的操作行为。

PUT代表一个新建一个资源,POST表示更新一个资源,GET表示查看一个资源,DELETE表示删除一个资源。

  • 路径解析
  • cookie

性能的影响:一旦服务器向客户端附送了设置Cookie的意图,除非Cookie过期,否则客户端每次请求都会发送这些cookie到服务端,cookie过多会导致报头较大。

YSlow性能规则:

减小Cookie的大小

为静态组件使用不同的域名,将域名转换IP需要进行DNS查询,多一个域名就会多一次DNS查询

减少DNS查询

  • session

如何将每一个客户和服务器中的数据一一对应起来:

基于cookie来实现用户和数据的映射

服务器开启了session,将约定一个键值作为session的口令,服务器没有检查到用户请求cookie中携带,将会生成一个值唯一不重复,并设置超时时间。

通过查询字符串来实现浏览器和服务器端数据的对应

风险:因为要将地址栏中的地址发送给另外一个人,那么他就和你相同的身份。cookie的方案在换了浏览器或者电脑之后无法生效,较为安全。

  • session集中化的工具:redis,memcached等,Node进程无须在内部维护数据对象,垃圾回收和内存限制的问题,并且告诉的缓存设计的缓存过期策略更加合理。
  • 使用第三方缓存的原因

Node与缓存服务保持长链接,而非频繁的短连接,握手导致的延迟只影响初始化。

高速缓存直接在内存中进行数据存储和访问。

缓存服务通常与Node进程运行在相同的机器上或者相同的机房里,网络速度受到的影响较小。

session需要异步的方式获取。

  • 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);
    })
  }
}
  • 由于是文件上传,那么像普通表单,json,xml那样先接收内容在解析的方式变得不可接受。

接收大小未知的数据量的时候:

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文件可能无法完成解析。

  • CSRF
// 检测
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模型的主要思想是将业务逻辑按职责分离

控制器

模型

视图

工作模式:

路由解析,行为调用相关的模型进行数据操作,数据操作结束后调用视图和相关数据进行页面的渲染,输出到客户端。

你可能感兴趣的:(node,logger,egg)