koa技术分享


https://cnodejs.org/topic/56936889c2289f51658f0926
css


学习交流

项目同时部署在阿里云,访问地址www.keepforward.xyz:3000
gihub地址:https://github.com/a1511870876/myblog欢迎star
一些学习分享,你们共同交流,地址https://github.com/a1511870876/studyFileshtml

写在前面

Koa使用了ES6规范的generator和异步编程是一个更轻量级Web开发的框架,Koa 的先天优点在于 generator。因为是我我的的分享交流,因此Node基础、ES6标准、Web开发基础以及Koa的"Hello World"程序都不在讨论,但愿各位小伙伴提出意见和指导。
  PS:Koa 内核中没有捆绑任何中间件,但不用担忧,Koa 拥有极其强悍的拓展性,正文全部中间件均可以在npm官网下载安装,但国内域名安装会有一些限制,提供一个国内镜像安装方法,速度很是快,在直接npm模块失败的时候很是好用,使用npm --registry=http://registry.npmjs.org install XXXXX –XX 命令安装,只须要在install后面加上要安装的中间件名称和相应的参数便可。node

1、使用Koa搭建Web项目流程

一、Koa项目建立
  我的认为无论任何框架,Web项目搭建必需的几个方面,页面、中间件、路由、会话和存储、日志、静态文件指定,以及错误的处理。固然,网站开发不止这些东西,还有许多主题,好比实时通信,搜索引擎架构,权限控制,邮件优先队列,日志记录分析,对Web开发还刚刚入门属于菜鸟级别,这里就不作深刻的讨论了。了解Express框架的小伙伴必定知道Express的部署过程,无论是经过express-generator生成仍是WebStorm等编译器直接建立,它的目录结构大概是这样的:git

|——app.js |——bin |——node_modules |——package.json |——public |——routes |——views

*app.js,是程序启动文件
  *bin,存放执行程序
  *node_modules,存放项目依赖库
  *package.json,是配置和一些相关信息
  *public,存放静态文件(css,js,img)
  *routes,存放路由文件
  *views,存放前台页面文件
  这些结构基本包含了上述提到的Web项目搭建的要素,可是目前相似express-generator的Koa部署工具Koa-generator(非官方)并不完善而且我的测试存在些许错误。其实Koa-generator也是仿造上述express-generator生成的目录,既然这样还不如手动建立目录来的爽快(generator-k是另外一款生成器,用上去感受还行),在根目录新建app.js做为程序的启动文件,建立三个文件夹分别命名public、routes和views,最后新建package.json文件存放你的项目的一些信息。完成这些建立以后,用npm命令安装Koa,这样的话一个基本的Koa框架就搭建好了,很是的的轻量级,它的目录结构以下:程序员

 |——app.js |——node_modules |——public | |——img | |——css | |——js | |——routes | |——index.js | |——user.Js | |——views | |——_layout.html | |——index.html | |——package.json Koa项目运行:node --harmony app.js 必须加 --harmony ,这样才会支持 ES6 语法。

二、Koa日志
  日志是项目error调试和平常维护的基本手段,Koa有日志模块Koa-logger,npm install Koa-logger后使用app.use(logger());命令程序就会在控制台自动打印日志,固然若是你对Koa-logger的风格不满意或者想要看到更多得信息也能够本身编辑代码实现有本身风格的日志打印。
例如:es6

 auto map route -> [get]/authority/saveAddUser/ auto map route -> [get]/authority/searchUserInfo/ auto map route -> [get]/authority/updateUser/ auto map route -> [get]/authority/deletedUser/ auto map route -> [get]/authority/getSelectValues/ auto map route -> [get]/authority/saveAuthority/

最后呢,若是有须要,要把日志进行存储。
三、Koa的错误处理
  Koa 有 error 事件,当发生错误时,能够经过该事件,对错误进行统一的处理。github

var Koa = require('koa'); var app = Koa(); app.on('error', function(err,ctx){ console.log(err); }); app.listen(3000);

上面这段代码在若是捕获到错误,页面会打印出 “Internal Server Error” (这是Koa对错误的默认处理)。这个错误咱们在综合监控系统中也常常见到,那么咱们显然没法根据这条日志获得什么信息web

TypeError: Cannot read property 'split' of undefined at Object.Home.index (d:\test\route\home.js:143:31) at GeneratorFunctionPrototype.next (native) at Object.dispatch (d:\test\node_modules\koa-router\lib\router.js:97:44) at GeneratorFunctionPrototype.next (native)

这些错误信息是怎么报出来的的呢,实际上是Koa-onerror 中间件,它优化错误信息,根据这些错误信息就能更好的捕获到错误。Koa-onerror使用方法:redis

 var onerror = require('Koa-onerror'); onerror(app);

四、Koa静态文件指定
  Koa静态文件指定中间件Koa-static,npm install Koa-static以后就可使用Koa-static负责托管 Koa 应用内的静态资源。映射了静态文件目录,引用的时候直接去该目录下寻找资源,会减小一些消耗。(不知道讲的准确不许确,只是我的的理解)指定public为静态文件目录的代码以下:sql

 var staticServer = require('koa-static'); var path = require('path'); app.use(staticServer(path.join(__dirname,'public')));

五、ejs模板的使用
  渲染页面须要一种模板,这里选择风格接近html的ejs模板。npm install Koa-ejs后就能够在Koa框架中使用ejs模版。

 var render = require('koa-ejs'); render(app, { root: path.join(__dirname, 'views'), layout: '__layout', viewExt: 'html', cache: false, debug: true }); app.use(function *(){ yield this.render('index',{layout:false}); });

六、Koa路由设置
  Koa个极简的web框架,简单到连路由模块都没有配备。本身手写路由是这样的:

 app.use(function *(){ //我是首页 if(this.path==='/'){ } });

使用更增强大的路由中间件,Koa中设置路由通常安装Koa-router,Koa-router支持五种方法

 router.get() router.post() router.put() router.del() router.patch()

GET方法举例:

 var app = require('koa')(); var Router = require('koa-router'); var myRouter = new Router(); myRouter.get('/', function *(next) { yield this.render('index',{layout:false}); }); app.use(myRouter.routes()); app.listen(3000);

Koa-router 拥有丰富的 api 细节,用好这些 api ,可让页面代码更为优雅与可维护。接收query参数

 http://localhost:3000/?a=1(条件) index.js var router = require('koa-router')(); router .get('/',function *(next){ console.log(this.query); yield this.render('index',{layout:false}); }) .get('/home',function *(ctx,next){ ctx.render('home'); }); //ctx为Koa2.0中支持 ... ... module.exports = router; 控制台打印: <-- GET /?a=1 { a: '1' } { a: '1' } 接收params参数 http://localhost:3000/users/123(参数) router.get('/user/:id', function *(next) { console.log(this.params.id); });

param() 用于封装参数处理中间件,当访问 /detail/:id 路由时,会先执行 param() 定义的 generator function 逻辑。函数的第一个是路由参数的值,next 是中间件流程关键标识变量。
yield next;
  表示执行下一个中间件。

 app.param('id',function *(id,next){ this.id = Number(id); if ( typeof this.id != 'number') return this.status = 404; yield next; }).get('/detail/:id', function *(next) { //我是详情页面 var id = this.id; //123 this.body = id; });

七、Koa中间件
  Koa的中间件很像Express的中间件,也是对HTTP请求进行处理的函数,可是必须是一个Generator函数即 function *(){} 语法,否则会报错。能够这么说,Nodejs的Web程序中任何请求和响应都是中间件在操做。

 app .use(logger()) //日志中间件 .use(serve(__dirname + '/public')) //静态文件指定中间件 .use(router.routes()) //路由中间件 .use(router.allowedMethods()); //路由中间件

app.use 加载用于处理http请求的middleware(中间件),当一个请求来的时候,会依次被这些 middlewares处理。执行的顺序是你定义的顺序。中间件的执行顺序规则是相似“栈”的结构,全部须要执行的中间件都被一个一个放入“栈”中,当没有遇到next()的时候,“栈”里边的这些中间件被逆序执行。

 app.use(function *(next){ this; // is the Context this.request; // is a Koa Request this.response; // is a Koa Response });

说明:
  •this是上下文
  •*表明es6里的generator
  http模型里的请求和响应
  •this.request
  •this.response
  app.use() 究竟发生了什么难以想象的化学反应呢?其实 app.use() 就干了一件事,就是将中间件放入一个数组,真正执行逻辑的是:app.listen(3000);Koa 的 listen() 除了指定了 http 服务的端口号外,还会启动 http server,等价于:

 var http = require('http'); http.createServer(app.callback()).listen(3000);

后面这种繁琐的形式有什么用呢?
  一个典型的场景是启动 https 服务,默认 app.listen(); 是启动 http 服务,启动 https 服务就须要:

 var https = require('https'); https.createServer(app.callback()).listen(3000);

2、异步编程

一、异步流程控制
  异步编程对 JavaScript 语言过重要。JavaScript 只有一根线程,若是没有异步编程,根本无法用,非卡死不可。
  之前,异步编程的方法,大概有下面四种。
  回调函数
  事件监听
  发布/订阅
  Promise 对象
  JavaScript 语言对异步编程的实现,就是回调函数。所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到从新执行这个任务的时候,就直接调用这个函数。它的英语名字 callback,直译过来就是"从新调用"。读取文件进行处理,是这样写的。

 fs.readFile('/etc/passwd', function (err, data) { if (err) throw err; console.log(data); });

上面代码中,readFile 函数的第二个参数,就是回调函数,也就是任务的第二段。等到操做系统返回了 /etc/passwd 这个文件之后,回调函数才会执行。回调函数自己并无问题,它的问题出如今多个回调函数嵌套。假定读取A文件以后,再读取B文件,代码以下。

 fs.readFile(fileA, function (err, data) { fs.readFile(fileB, function (err, data) { // ... }); });

不难想象,若是依次读取多个文件,就会出现多重嵌套。代码不是纵向发展,而是横向发展,很快就会乱成一团,没法管理。这种状况就称为"回调函数噩梦"(callback hell)。Promise就是为了解决这个问题而提出的。它不是新的语法功能,而是一种新的写法,容许将回调函数的横向加载,改为纵向加载。采用Promise,连续读取多个文件,写法以下。

 var readFile = require('fs-readfile-promise'); readFile(fileA) .then(function(data){ console.log(data.toString()); }) .then(function(){ return readFile(fileB); }) .then(function(data){ console.log(data.toString()); }) .catch(function(err) { console.log(err); });

上面代码中,我使用了 fs-readfile-promise 模块,它的做用就是返回一个 Promise 版本的 readFile 函数。Promise 提供 then 方法加载回调函数,catch方法捕捉执行过程当中抛出的错误。能够看到,Promise 的写法只是回调函数的改进,使用then方法之后,异步任务的两段执行看得更清楚了,除此之外,并没有新意。
  Promise 的最大问题是代码冗余,原来的任务被Promise 包装了一下,无论什么操做,一眼看去都是一堆 then,原来的语义变得很不清楚。
  那么,有没有更好的写法呢?
  ECMAScript 6 (简称 ES6 )做为下一代 JavaScript 语言,将 JavaScript 异步编程带入了一个全新的阶段。异步编程的语法目标,就是怎样让它更像同步编程。
  Koa 的先天优点在于 generator。
  generator指的是

 function* xxx(){ }

是es6里的写法。

 var r = 3; function* infinite_ap(a) { for( var i = 0; i < 3 ; i++) { a = a + r ; yield a; } } var sum = infinite_ap(5); console.log(sum.next()); // returns { value : 8, done : false } console.log(sum.next()); // returns { value : 11, done: false } console.log(sum.next()); // returns { value : 14, done: false } console.log(sum.next()); //return { value: undefined, done: true }

yield语句就是暂停标志,next方法遇到yield,就会暂停执行后面的操做,并将紧跟在yield后面的那个表达式的值,做为返回对象的value属性的值。当下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句。若是没有再遇到新的yield语句,就一直运行到函数结束,将return语句后面的表达式的值,做为value属性的值,若是该函数没有return语句,则value属性的值为undefined。当第一次调用 sum.next() 时 返回的a变量值是5 + 3,同理第二次调用 sum.next() ,a变量值是8 +3,知道循环执行结束,返回done:true标识。你们有没有发现个问题,Koa 中 generator 的用法与上述 demo 演示的用法有很是大得差别,那是由于 Koa 中的 generator 使用了 co 进行了封装。
二、co的使用
  Ps:(这里只是简单介绍,后续能够做为一个专题来说)
  co 函数库是著名程序员 TJ Holowaychuk 于2013年6月发布的一个小工具,用于 Generator 函数的自动执行。
  好比,有一个 Generator 函数,用于依次读取两个文件。

 var gen = function* (){ var f1 = yield readFile('/etc/fstab'); var f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); };

co 函数库可让你不用编写 Generator 函数的执行器。

 var co = require('co'); co(gen);

上面代码中,Generator 函数只要传入 co 函数,就会自动执行。
  co 函数返回一个 Promise 对象,所以能够用 then 方法添加回调函数。

 co(gen).then(function (){ console.log('Generator 函数执行完成'); })

上面代码中,等到 Generator 函数执行结束,就会输出一行提示。
  为何 co 能够自动执行 Generator 函数?
  前面文章说过,Generator 函数就是一个异步操做的容器。它的自动执行须要一种机制,当异步操做有告终果,可以自动交回执行权。
  两种方法能够作到这一点。
  (1)回调函数。将异步操做包装成 Thunk 函数,在回调函数里面交回执行权。
  (2)Promise 对象。将异步操做包装成 Promise 对象,用 then 方法交回执行权。
  co 函数库其实就是将两种自动执行器(Thunk 函数和 Promise 对象),包装成一个库。使用 co 的前提条件是,Generator 函数的 yield 命令后面,只能是 Thunk 函数或 Promise 对象。
  参考:http://www.ruanyifeng.com/blog/2015/05/co.html
三、Koa 中间件机制实现原理
使用 Koa 的同窗必定会有以下疑问:

  1. Koa 的中间件机制是如何实现?
  2. 为何中间件必须是 generator function?
  3. next 实参指向是什么?为何能够经过 yield next 能够执行下一个中间件?
  4. 为何中间件从上到下执行完后,能够从下到上执行 yield next 后的逻辑?

经过实现简单的 Koa 框架(剥离除中间件外全部的逻辑)来解答上述问题,这个框架的名字叫 SimpleKoa:

 var co = require('co'); function SimpleKoa(){ this.middlewares = []; } SimpleKoa.prototype = { //注入个中间件 use: function(gf){ this.middlewares.push(gf); }, //执行中间件 listen: function(){ this._run(); }, _run: function(){ var ctx = this; var middlewares = ctx.middlewares; return co(function *(){ var prev = null; var i = middlewares.length; //从最后一个中间件到第一个中间件的顺序开始遍历 while (i--) { //实际Koa的ctx应该指向server的上下文,这里作了简化 //prev 将前面一个中间件传递给当前中间件 prev = middlewares[i].call(ctx, prev); } //执行第一个中间件 yield prev; })(); } };

写个 demo 印证下中间件执行顺序:

 var app = new SimpleKoa(); app.use(function *(next){ this.body = '1'; yield next; this.body += '5'; console.log(this.body); }); app.use(function *(next){ this.body += '2'; yield next; this.body += '4'; }); app.use(function *(next){ this.body += '3'; }); app.listen();

执行后控制台输出:123456,对照 Koa 中间件执行顺序,彻底一致!寥寥几行代码,咱们就实现了 Koa 的中间件机制!这就是 co 的魔力。

3、Koa中涉及但本次没有讲的问题

一、Koa中的cookie和session(后续详细讲解)
  web应用程序都离不开cookie和session的使用,是由于Http是一种无状态性的协议。保存用户状态信息的一种方法或手段,Session 与 Cookie 的做用都是为了保持访问用户与后端服务器的交互状态。
二、Koa中nosql(后续技术分享会详细讲解)
  mongodb是一个基于文档的非关系型数据库,全部数据是从磁盘上进行读写的,其优点在于查询功能比较强大,能存储海量数据。
  redis是内存型数据库,数据保存在内存中,经过tcp直接存取,优点是速度快,并发高,缺点是数据类型有限,查询功能不强,通常用做缓存。它由C语言实现的,与 NodeJS工做原理近似,一样以单线程异步的方式工做,先读写内存再异步同步到磁盘,读写速度上比MongoDB有巨大的提高,当并发达到必定程度时,便可考虑使用Redis来缓存数据和持久化Session。

 var mongoose = require('mongoose'); // 引入 mongoose 模块 mongoose.connect('mongodb://localhost/blog'); // 而后链接对应的数据库:mongodb://localhost/test // 其中,前面那个 mongodb 是 protocol scheme 的名称;localhost 是 mongod 所在的地址; // 端口号省略则默认链接 27017;blog是数据库的名称 // mongodb 中不须要创建数据库,当你须要链接的数据库不存在时,会自动建立一个出来。 module.exports = mongoose; // 导出 mongoose 模块 var mongoose = require('../modules/db'); // 引入 mongoose 模块 var User = mongoose.model('User',{ name: {type: String, match: /^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/}, password: String }); //建立了一个名为 User 的 model var user1 = new User({name:'12345@qqqqqq.com'}); user1.password = 'a5201314'; user1.save(function(err){ if(err){ console.log("save error"); } });