koa 框架
现在很多项目都是基于 koa 框架实现的, 主要是因为 koa 小巧轻便, 采用插件式扩展, 可以根据需要的功能来选用不同的插件, 开发起来更加的方便快捷. 所以了解 koa 的实现原理是十分, 十分, 十分有必要的.
koa 框架会用也会写 -(koa-router)
koa 的使用分析
- const Koa = require('koa');
- let App = new Koa();//Koa 是一个类, 通过 new 生成一个实例
- //koa 的原型上有 use 方法, 来注册中间件
- App.use((ctx,next)=>{
- //koa 拥有 ctx 属性, 上面挂载了很多属性
- console.log(ctx.req.path);
- console.log(ctx.request.req.path);
- console.log(ctx.request.path);
- console.log(ctx.path);
- next();// 洋葱模型, 中间件组合
- })
- App.listen(3000);//Koa 的原型上拥有监听 listen
洋葱模型和中间件组合
洋葱模型
- const Koa = require('koa');
- const App = new Koa();
- App.use(async (ctx, next)=>{
- console.log(1)
- await next();
- console.log(2)
- });
- App.use(async (ctx, next) => {
- console.log(3)
- await next();
- console.log(4)
- })
- App.use(async (ctx, next) => {
- console.log(5)
- awit next();
- console.log(6)
- })
- // 打印结果: 1 3 5 6 4 2
中间件组合
koa 洋葱模型的实现, 其实就是通过 use 将函数存放在一个 middlewares 队列中, 然后通过函数 dispatch 派发中间件.
dispatch 组合中间件:
- let App = {
- middlewares:[]; // 缓存队列
- use(fn){ // 注册中间件
- this.middlewares.push(fn);
- }
- }
- App.use(next=>{
- console.log(1)
- next();
- console.log(2)
- });
- App.use(next => {
- console.log(3)
- next();
- console.log(4)
- })
- App.use(next => {
- console.log(5)
- next();
- console.log(6)
- })
- dispatch(0)
- function dispatch(index){ // 派发执行中间件
- if(index===App.middlewares.length) retrun ;
- let middleware = App.middlewares[index];
- middleware(()=>{
- dispatch(index+1);
- })
- }
Array.prototype.reduceRight 组合中间件:
- let App = {
- middlewares:[];// 缓存队列
- use(fn){// 注册中间件
- this.middlewares.push(fn);
- }
- }
- App.use(next=>{ //fn1(next) next => fn2
- console.log(1)
- next();
- console.log(2)
- });
- App.use(next => { //fn2(next) next => fn3
- console.log(3)
- next();
- console.log(4)
- })
- App.use(next => { //fn3(next) next => null;
- console.log(5)
- next();
- console.log(6)
- })
- let fn= compose(App.middlewares)
- function conpose(middle){
- return middles.reduceRight((a,b)=>{ // 收敛成一个函数
- return function(){
- b(a);
- }
- },()=>{});
- }
- fn();
- //fn3(next) next:() => {};
- //fn2(next) next:() => fn3(()=>{})
- //fn1(next) next:() => fn2(()=>fn3(()=>{}))
Array.prototype.reduce 组合中间件:
- let App = {
- middlewares:[];// 缓存队列
- use(fn){// 注册中间件
- this.middlewares.push(fn);
- }
- }
- App.use(next=>{ //fn1(next) next => fn2
- console.log(1)
- next();
- console.log(2)
- });
- App.use(next => { //fn2(next) next => fn3
- console.log(3)
- next();
- console.log(4)
- })
- App.use(next => { //fn3(next) next => null;
- console.log(5)
- next();
- console.log(6)
- })
- let fn= compose(App.middlewares)
- function conpose(middle){
- return middles.reduce((a,b)=>{ // 收敛成一个函数
- return (arg)=>{
- a(()=>{b(arg)})
- }
- });
- }
- fn(()=>{});
koa 的组成部分
koa 主要是由四部分组成:
application:koa 的主要逻辑, 包含了中间件处理过程
context:koa 关于 ctx 的封装
request:koa 请求对象的封装
response:koa 响应对象封装
koa 的实现
Koa 类初始化
Koa 是一个类, 拥有 middleware,ctx,request,response
Koa.prototype 拥有 use 注册中间件
Koa.prototype 拥有 listen 监听网络请求, 其内部是对 http 模块的封装
Koa 中 handleRquest 处理上下文 ctx 和中间件 middleware
- //application.JS
- const http = require('http');
- let context = require('./context');
- let request = require('./request');
- let response = require('./response');
- class Koa {
- constructor(){
- this.middlewares = [];
- // 原型继承, 防止引用空间的问题使后加的属性也会加在这个对象上
- //this.context 和引入的 context 不是同一个对象
- this.context = Object.create(context);
- this.request = Object.create(request);
- this.response = Object.create(response);
- }
- use(fn){
- this.middlewares.push(fn) ;
- }
- // 挂载封装处理 ctx
- createContext(req,res){
- let ctx = this.context;
- ctx.request = this.request;
- ctx.response = this.response;
- ctx.req=ctx.request.req =req;
- ctx.res=ctx.response.res=res;
- return ctx;
- }
- // 组合中间件
- compose(ctx,middles){
- function dispatch(index){
- if(index === middle.length) return;
- let middle = middles[index];
- middle(ctx,()=>dispatch(index+1));
- }
- dispatch(0);
- }
- // 网络请求监听回调
- handleRequest(req,res){
- let ctx = createContext(req,res);
- this.compose(ctx,this.middlewares);
- }
- listen(...args){
- let server = http.createServer(this.handleRquest);
- server.listen(...args)
- }
- }
- module.exports = Koa
request 封装
request 上扩展 url,path 等属性
- //request.JS
- let request = {
- // 类似 Object.defineProperty(request,'url'){get(){}}
- get url(){
- //this.req => ctx.request.req = req, 调用时 ctx.request.url
- this.req.url;
- }
- get path(){
- let url = require('url');
- return url.parse(this.req.url).pathname;
- }
- }
- module.exports = request;
response 封装
request 上扩展 body 等属性
- //response.JS
- let response = {
- get body(){
- return this._body;
- }
- set body(val){ // 设置内置的_body 来存储
- this._body=val
- }
- }
- module.exports = response;
ctx 封装
ctx 属性代理了一些 ctx.request,ctx.response 上的属性, 使得 ctx.xx 能够访问 ctx.request.xx 或 ctx.response.xx
- //context.JS
- let proto = {
- };
- function defineGetter(property,key){
- proto.__defineGetter(key,function(){
- return this[property][key];
- })
- }
- function defineSetter(property,key){
- proto.__defineSetter(key,function(val){
- this[property][key] = val;
- })
- }
- defineGetter('request','url'); //ctx 代理了 ctx.request.url 的 get
- defineGetter('request','path'); //ctx 代理了 ctx.request.path 的 get
- defineGetter('response','body'); //ctx 代理了 ctx.response.body 的 get
- defineSetter('response','body'); //ctx 代理了 ctx.response.body 的 set
- module.exports = proto;
处理异步和错误
上面的功能都是基于同步函数, 但是在 node 中大多数都是异步函数, 所以这里面中间件的处理函数需要兼容异步函数. 因为 async+awit 等于 generator+co(koa1.0), 而 co 中实现 generator 自动化是基于 Promise 实现的, 所以这里必须函数 promise 化. 如果不了解 Promise,generator,async 可以看看另一篇文章 promise 原理就是这么简单
- //application.JS
- const http = require('http');
- let context = require('./context');
- let request = require('./request');
- let response = require('./response');
- let Stream = require('stream');
- let EventEmitter = require('events');
- class Koa extends EventEmitter { // 继承 EE, 处理错误
- constructor(){
- this.middlewares = [];
- this.context = Object.create(context);
- this.request = Object.create(request);
- this.response = Object.create(response);
- }
- use(fn){
- this.middlewares.push(fn) ;
- }
- createContext(req,res){
- let ctx = this.context;
- ctx.request = this.request;
- ctx.response = this.response;
- ctx.req=ctx.request.req =req;
- ctx.res=ctx.response.res=res;
- return ctx;
- }
- compose(ctx,middles){
- function dispatch(index){
- // 没有注册中间件, 返回一个 promise
- if(index === middle.length) return Promise.resolve();
- let middle = middles[index];
- // Promise 化, next 一定为 promise
- return Promise.resolve(middle(ctx,()=>dispatch(index+1)));
- }
- return dispatch(0);
- }
- handleRequest(req,res){
- res.statusCode = 404;
- let ctx = createContext(req,res);
- // 所有的中间件执行时候, 可以执行内置逻辑, 处理错误等
- let p = this.compose(ctx,this.middlewares);
- p.then(()=>{
- // 统一处理 res.body 的不同情况
- let body = ctx.body;
- if (Buffer.isBuffer(body) || typeof body === 'string'){
- res.setHeader('Content-Type','text/plain;charset=utf8')
- res.end(body);
- } else if (body instanceof Stream){
- body.pipe(res);
- }else if(typeof body == 'object'){
- res.setHeader('Content-Type','application/JSON;charset=utf8')
- res.end(JSON.stringify(body));
- }else{
- res.end('Not Found');
- }
- }).catch(e=>{ // 处理错误
- this.emit('error',e);
- res.statusCode = 500;
- //_http_server 可以根据状态码找到对应的类型字段
- res.end(require('_http_server').STATUS_CODES[res.statusCode]);
- })
- }
- listen(...args){
- let server = http.createServer(this.handleRquest);
- server.listen(...args)
- }
- }
- module.exports = Koa
结语
koa 的原理基本就介绍完了, koa 还有一个重要的部分就是中间件, 很多功能都是中间件实现的, 后面一起学习 kao 的中间件:
koa 框架会用也会写 -(koa-router)
来源: https://juejin.im/post/5bab0c415188255c8473b123