koa2源码学习

koa

Koa 是⼀个新的 web 框架, 致⼒于成为 web 应⽤和 API 开发领域中的⼀个更⼩、更富有表现⼒、更健壮的基⽯。
特点

  • 轻量,无捆绑
  • 中间件架构
  • 优雅的API设计
  • 增强的错误处理

安装

npm i koa -s

简单的写法

const Koa = require('koa')
const app = new Koa();
app.use((ctx,next)=>{
  ctx.body=[
    {
      content:'leo'
    }
  ];
  next()
})
app.use((ctx,next)=>{
  if(ctx.url === '/index'){
    ctx.type = 'text/html;charset=utf-8';
    ctx.body=`<h1>koa</h1>`
  }
})
app.listen(3000,()=>{ 
  console.log(`start server at localhost:3000`)
})
路由

安装koa-router

npm i koa-router -s
const Koa = require('koa')
const app = new Koa();
const Router = require('koa-router')
const router = new Router()
app.use(router.routes())
router.get('/',async (ctx,next)=>{
  ctx.body = [
    {
      content:'leo'
    }
  ]
})
router.get('/index',async(ctx,next)=>{
  ctx.body=[
    {
      content:'index page'
    }
  ]
})
app.listen(3000,()=>{ 
  console.log(`start server at localhost:3000`)
})

koa原理

⼀个基于nodejs的⼊⻔级http服务,koa的⽬标是⽤更简单化、流程化、模块化的⽅式实现回调部分。

Koa 不在内核方法中绑定任何中间件,它仅仅提供了一个轻量的函数库,几乎所有功能都需要引用第 方中间件来实现。这样不仅能够使框架更加轻量、优雅,也能够让开发者充分发挥自己的想象力,根据业务和项目定制中间件。

根据koa原理实现一个简单的类库:
index.js

const http = require('http');

class Lau{
  listen(...args){
    const server = http.createServer((req,res)=>{
      this.callback(req,res)
    })
    server.listen(...args)
  }
  use(callback){ // 使用中间件
    this.callback = callback
  }
}

const app = new Lau();
app.use((req,res)=>{
  res.writeHead(200);
  res.end('hi lau')
})
app.listen(3000,()=>{
  console.log(`start server at localhost:3000`)
})

上面的代码对请求进行了非常简单的封装。

来看一下koa中非常重要的概念:context

context对象

Koa将Node.js的Request(请求〉和 Response (响应)对象封装到 Context 对象中,所以也可以把 Context 对象称为一次对话的上下文,在context上设置getter和setter,从⽽简化操作。

封装request、response、context
request.js

module.exports = {
  get url(){
    return this.req.url
  },
  get method(){
    return this.req.method.toLowerCase()
  }
}

只简单展示几个,其他类似。

response.js

module.exports={
  get body(){
    return this._body
  },
  set body(val){
    this._body = val
  },
}

context.js

module.exports={
  get url(){
    return this.request.url
  },
  get body(){
    return this.response.body
  },
  set body(val){
    this.response.body = val
  },
  get method(){
    return this.request.method
  }
}

在index.js文件中引入上面三个文件,进行改进。

const http = require('http');
const context = require('./nativeKoa/context')
const request = require('./nativeKoa/request')
const response = require('./nativeKoa/response')


class Lau{
  listen(...args){
    const server = http.createServer((req,res)=>{
      let ctx = this.createContext(req,res); // 创建上下文
      this.callback(ctx)
      res.end(ctx.body)
    })
    server.listen(...args)
  }
  use(callback){
    this.callback = callback
  }
  createContext(req, res) {
    const ctx = Object.create(context);
    ctx.request = Object.create(request);
    ctx.response = Object.create(response);
    ctx.req = ctx.request.req = req; 
    ctx.res = ctx.response.res = res; 
    return ctx;
  }
}

const app = new Lau();
app.use((ctx)=>{
  if(ctx.url === '/'){
    ctx.body='hello lau'
  }
})

app.listen(3000,()=>{
  console.log(`start server at localhost:3000`)
})

中间件

Koa中间件机制就是函数组合的概念,将⼀组需要顺序执⾏的函数复合为⼀个函数,外层函数的参数实际是内层函数的返回值。洋葱圈模型可以形象表示这种机制,是源码中的精髓和难点。

在这里插入图片描述
中间件函数是一个带有 ctx next 两个参数的简单函数。 ctx 就是之前章节介绍的上下文,封装了 Request Response 等对 next 用于把中间件的执行权交给下游的中间件。
在next()之前使用 await 关键字是因为 next()会返回一个 Promise 对象,而在当前中间件中位next()之后的代码会暂停执行,直到最后一个中间件执行完毕后,再自下而上依次执行每
个中间件中 next()之后的代码,类似于一种先进后出的堆栈结构 。
来看一下next()在koa中使用

app.use(async (ctx, next) => {
  console.log('one start ');
  await next();
  console.log(' one end ');
});
app.use(async (ctx, next) => {
  console.log(' two start ');
  ctx.body = 'two';
  await next();
  console.log('two end ');
});
app.use(async (ctx, next) => {
  console.log(' three start');
  await next();
  console.log(' three end ');
})
// 输出
one start
two start
three start
three end
two end
one end

实现中间件调用函数,将多个中间件组合成一个单一的中间件。

function compose(middlewares) {
  return function (ctx) { 
    return dispatch(0)
    function dispatch(i) {
      let fn = middlewares[i]
      if (!fn) {
        return Promise.resolve()
      }
      return Promise.resolve(
        fn(ctx,function next() { // 传入一个上下文
          return dispatch(i + 1)
        })
      )
    }
  }
}

将该函数添加至index.js中进行完善。

class Lau{
  constructor(){
    this.middlewares=[]
  }
  listen(...args){
    const server = http.createServer(async (req,res)=>{
      let ctx = this.createContext(req,res); // 创建上下文
      const fn = this.compose(this.middlewares)
      console.log(fn.toString())
      await fn(ctx)
      res.end(ctx.body)
    })
    server.listen(...args)
  }
  use(middleware){
    this.middlewares.push(middleware)
  }
  createContext(req, res) {
    const ctx = Object.create(context);
    ctx.request = Object.create(request);
    ctx.response = Object.create(response);
    ctx.req = ctx.request.req = req; 
    ctx.res = ctx.response.res = res; 
    return ctx;
  }
  compose(middlewares) {
    return function (ctx) { 
      return dispatch(0)
      function dispatch(i) {
        let fn = middlewares[i]
        if (!fn) {
          return Promise.resolve()
        }
        return Promise.resolve(
          fn(ctx,function next() { 
            return dispatch(i + 1)
          })
        )
      }
    }
  }
}

常见中间件的实现

规范:

  • 异步函数
  • 接收ctxnext参数
  • 任务结束后执行next()

实现一个简单的路由中间件

router.js

class Router{
  constructor(){
    this.stack=[]
  }
  register(path,methods,middleware){
    let route = {path,methods,middleware}
    this.stack.push(route)
  }
  get(path,middleware){
    this.register(path,'get',middleware)
  }
  post(path,middleware){
    this.register(path,'post',middleware)
  }
  // 只实现常用的post、get方法
  routes(){
    let stock = this.stack;
    return async function (ctx,next){
      let currentPath = ctx.url;
      let route;
      for(let i =0;i<stock.length;i++){
        let item = stock[i];
        if(currentPath === item.path && item.methods.indexOf(ctx.method)>=0){
          route = item.middleware;
          break;
        }
      }
      if(typeof route === 'function'){
        route(ctx,next); // 执行对应操作
        return;
      }
      await next()
    }
  }
}

在index.js中进行改写

const Router = require('./router/index')
const router = new Router()
app.use(router.routes())

router.get('/',async (ctx,next)=>{
  ctx.body='hi router'
})
router.get('/index',async (ctx,next)=>{
  ctx.body='hi index page'
})
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章