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'
})
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章