Koa源碼

本系列文章是本人學習相關知識時所積累的筆記,以記錄自己的學習歷程,也爲了方便回顧知識;故文章內容較爲隨意簡練,抱着學習目的來的同學務必轉移他處,以免我誤人子弟~

koa的使用

創建後端服務器

原生的http模塊提供的方法:

const http = require('http');

const server = http.createServer((req, res) => {
  res.writeHead(200);
  res.end('hello world');
});

server.listen(3000, () => {
  console.log('server start at 3000');
});

在回調函數中,使用req和res對請求數據和響應數據進行處理
Koa創建服務:

const Koa = require('koa');
const app = new Koa();

app.use(async (ctx, next) => {
  let start = new Date().getTime();
  await next();
  let time = new Date().getTime() - start;
  ctx.set("X-Response-Time", `${time}ms`);
});

app.use(async (ctx, next) => {
  ctx.status = 200;
  ctx.body = 'Hello World';
  await next();
});

app.listen(3000);

Koa使用use方法接收中間件
ctx是對原生req和res的封裝,而next是中間件的關鍵,調用next實際上是跳出當前中間件進入下一個中間件

所謂中間件即是同步或異步函數

koa中間件的原理

  1. Koa將中間件按順序存放在數組中
  2. 每當中間件執行到next時,會進入下一個中間件,即內部實現將下一個中間件傳遞給next

本系列文章是本人學習相關知識時所積累的筆記,以記錄自己的學習歷程,也爲了方便回顧知識;故文章內容較爲隨意簡練,抱着學習目的來的同學務必轉移他處,以免我誤人子弟~

cookies用途

Koa中使用cookies模塊,封裝獲取前端請求的cookie和後端要求前端存儲cookie的功能;

app.use(async (ctx) => {
  // 設置cookie,前端接收到響應後將cookie存放本地
  ctx.cookies.set(
    'cookieName',
    'cookieValue',
    {
      domain: 'localhost',  // 寫cookie所在的域名
      path: '/index',       // 寫cookie所在的路徑
      maxAge: 10 * 60 * 1000, // cookie有效時長
      expires: new Date('2020-02-15'),  // cookie失效時間
      httpOnly: false,  // 是否只用於http請求中獲取
      overwrite: false  // 是否允許重寫
    }
  )
  // 獲取前端請求中名稱爲“otherCookieName”的cookie值
  ctx.cookies.get("otherCookieName");
})

cookies函數原理

keygrip模塊

js操作符:^ 異或
規則:位不相同時才爲1;


//http://codahale.com/a-lesson-in-timing-attacks/
var constantTimeCompare = function(val1, val2){
    if(val1 == null && val2 != null){
        return false;
    } else if(val2 == null && val1 != null){
        return false;
    } else if(val1 == null && val2 == null){
        return true;
    }

    if(val1.length !== val2.length){
        return false;
    }

    var result = 0;

    for(var i = 0; i < val1.length; i++){
        result |= val1.charCodeAt(i) ^ val2.charCodeAt(i); //Don't short circuit
    }

    return result === 0;
};

本系列文章是本人學習相關知識時所積累的筆記,以記錄自己的學習歷程,也爲了方便回顧知識;故文章內容較爲隨意簡練,抱着學習目的來的同學務必轉移他處,以免我誤人子弟~

delegate函數原理

Koa源碼中,是這麼使用delegate


/**
 * Response delegation.
 */

delegate(proto, 'response')
  .method('attachment')
  .method('redirect')
  .method('remove')
  .method('vary')
  .method('set')
  .method('append')
  .method('flushHeaders')
  .access('status')
  .access('message')
  .access('body')
  .access('length')
  .access('type')
  .access('lastModified')
  .access('etag')
  .getter('headerSent')
  .getter('writable');

/**
 * Request delegation.
 */

delegate(proto, 'request')
  .method('acceptsLanguages')
  .method('acceptsEncodings')
  .method('acceptsCharsets')
  .method('accepts')
  .method('get')
  .method('is')
  .access('querystring')
  .access('idempotent')
  .access('socket')
  .access('search')
  .access('method')
  .access('query')
  .access('path')
  .access('url')
  .access('accept')
  .getter('origin')
  .getter('href')
  .getter('subdomains')
  .getter('protocol')
  .getter('host')
  .getter('hostname')
  .getter('URL')
  .getter('header')
  .getter('headers')
  .getter('secure')
  .getter('stale')
  .getter('fresh')
  .getter('ips')
  .getter('ip');

實現的功能是將ctx.response和ctx.request的屬性代理到ctx上,即可以通過ctx.path訪問到ctx.request.path,可以通過ctx.body = someObj對ctx.response.body賦值someObj

看delegate源碼:

// delegate源碼
function Delegator(proto, target) {
  if (!(this instanceof Delegator)) return new Delegator(proto, target);
  this.proto = proto;
  this.target = target;
  this.methods = [];
  this.getters = [];
  this.setters = [];
  this.fluents = [];
}

第一句的作用是這個函數可以當成構造函數來使用,也可以想普通函數一樣掉用,如果像普通函數調用,則觸發第一句代碼,最終仍然返回實例,所以Koa源碼的用法也可改爲:

let delegate = new delegate(proto,"response");
delegate
	.access("body")
	...

method方法:代理方法

// delegate源碼
Delegator.prototype.method = function(name){
  var proto = this.proto;
  var target = this.target;
  this.methods.push(name);

  proto[name] = function(){
    return this[target][name].apply(this[target], arguments);
  };

  return this;
};

getter方法:代理屬性get行爲

// delegate源碼
Delegator.prototype.getter = function(name){
  var proto = this.proto;
  var target = this.target;
  this.getters.push(name);

  proto.__defineGetter__(name, function(){
    return this[target][name];
  });

  return this;
};

setter方法:代理屬性set行爲

// delegate源碼
Delegator.prototype.setter = function(name){
  var proto = this.proto;
  var target = this.target;
  this.setters.push(name);

  proto.__defineSetter__(name, function(val){
    return this[target][name] = val;
  });

  return this;
};

access方法:代理屬性get和set行爲

// delegate源碼
Delegator.prototype.access = function(name){
  return this.getter(name).setter(name);
};

.__defineSetter__.__defineGetter__現在已經快被廢棄,所以我們對於getter、setter和access方法可以進行如下改寫:

// getter
Delegator.prototype.getter = function(name){
  var proto = this.proto;
  var target = this.target;
  this.getters.push(name);

  Object.defineProperty(proto,name,{
    get(){
      return proto[target][name];
    }
  })
  return this;
}
// setter
Delegator.prototype.setter = function(name){
  var proto = this.proto;
  var target = this.target;
  this.setters.push(name);

  Object.defineProperty(proto,name,{
    set(value){
		proto[target][name] = value;
    }
  })
  return this;
}
// accsee
Delegator.prototype.access = function(name){
  Object.defineProperty(proto,name,{
    get(){
      return proto[target][name];
    },
    set(value){
      proto[target][name] = value;
    }})
  return this;
}

delegate源碼中還有個fluent方法,koa中沒有用到

// delegate源碼
Delegator.prototype.fluent = function (name) {
  var proto = this.proto;
  var target = this.target;
  this.fluents.push(name);

  proto[name] = function(val){
    if ('undefined' != typeof val) {
      this[target][name] = val;
      return this;
    } else {
      return this[target][name];
    }
  };

  return this;
};

作用跟access相近,但是改變了取值和賦值方式:

// access
ctx.body // 返回 ctx.response.body 的值
ctx.body = someObj // ctx.response.body 的值變爲 someObj

// fluent
ctx.body() // 返回 ctx.response.body 的值
ctx.body(someObj) // ctx.response.body 的值變爲 someObj

本系列文章是本人學習相關知識時所積累的筆記,以記錄自己的學習歷程,也爲了方便回顧知識;故文章內容較爲隨意簡練,抱着學習目的來的同學務必轉移他處,以免我誤人子弟~

compose函數原理

compose實現:


function compose (middleware) {
  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
  for (const fn of middleware) {
    if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
  }

  return function (context, next) {
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i]
      if (i === middleware.length) fn = next // 這裏的next永遠爲undefined,因爲調用該函數時沒有傳next參數
      if (!fn) return Promise.resolve()
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

compose函數接收中間件數組,返回一個函數,這個函數利用內部定義的dipatch函數遞歸遍歷中間件(這個函數最終在 Koa.prototype.handleRequest中調用)

個人感覺Koa在這邊處理方式有點繞
更直觀的可以這麼寫:

handleRequest(req, res) {
    let ctx = this.createContext(req, res);
    let fn = this.compose(this.middlewares, ctx);
    fn.then((value)=>handleResponse(ctx)).catch((err)=>{...});
}
createContext(req, res) {
    let ctx = Object.create(this.context);
    const request = ctx.request = Object.create(this.request);
    const response = ctx.response = Object.create(this.response);
    ctx.req = request.req = response.req = req;
    ctx.res = request.res = response.res = res;
    request.ctx = response.ctx = ctx;
    request.response = response;
    response.request = request;
    return ctx;
}
compose(middlewares, ctx) {
    function dipatch(index) {
        if (index === middlewares.length) {
            return Promise.resolve();
        }
        let middleware = middlewares[index];
        return Promise.resolve(middleware(ctx, () => dipatch(index + 1)));
    }
    return dipatch(0);
}
handleResponse(ctx){
    // ....
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章