Koa上下文Context的數據劫持

這幾天我一直在思索,關於Koa我能夠分享什麼?
分享Koa中間件實現原理?
上一篇的《從源碼上理解express中間件》已經解釋的比較完善了,Koa中間件實現也比較類似,只不過,是把中間件函數抽離出來爲koa-compose,循環遍歷的時候將函數外邊套了tryCatch,裏面加了Promise.resolve(fn)而已。javascript

所以,這篇文章主要分享下Koa的數據劫持。java

如: 如下訪問器和 Request 別名等效。node

ctx.header
ctx.headers
ctx.method
ctx.method=
ctx.url
ctx.url=
ctx.originalUrl
ctx.origin
ctx.href
ctx.path
ctx.path=
ctx.query
ctx.query=
ctx.querystring
ctx.querystring=
ctx.host
ctx.hostname
ctx.fresh
ctx.stale
ctx.socket
ctx.protocol
ctx.secure
ctx.ip
ctx.ips
ctx.subdomains
ctx.is()
ctx.accepts()
ctx.acceptsEncodings()
ctx.acceptsCharsets()
ctx.acceptsLanguages()
ctx.get()

複製代碼

上下文Context的建立

Koa的源碼主要分爲四個部分:

  • application
  • context
  • request
  • response

application是繼承自Node核心模塊events,經過實例化Application,獲得Koaapplicationconstructor的時候會經過Object.create()建立一個新對象,帶着指定的原型對象和屬性
點擊這裏查看MDNgit

constructor() {
    super();

    this.proxy = false;
    //中間件數組
    this.middleware = [];
    this.subdomainOffset = 2;
    //設置環境變量,默認development
    this.env = process.env.NODE_ENV || 'development';
    //使用現有的對象來提供新建立的對象的__proto__,即this.context.__proto__ === context //true
    this.context = Object.create(context);
    this.request = Object.create(request);
    this.response = Object.create(response);
    if (util.inspect.custom) {
      this[util.inspect.custom] = this.inspect;
    }
  }
複製代碼

當用戶執行app.listen時,調用callback函數,中間件函數的執行和調用createContext()github

//啓動Koa服務器
listen(...args) {
  debug('listen');
  const server = http.createServer(this.callback());
  return server.listen(...args);
}

//callback
callback() {
//處理Koa的中間件。
const fn = compose(this.middleware);

if (!this.listenerCount('error')) this.on('error', this.onerror);

const handleRequest = (req, res) => {
  const ctx = this.createContext(req, res);
  return this.handleRequest(ctx, fn);
};

return handleRequest;
}
//建立context
createContext(req, res) {
  const context = Object.create(this.context);
  const request = context.request = Object.create(this.request);
  const response = context.response = Object.create(this.response);
  //設置context的app、req、res、res、ctx
  context.app = request.app = response.app = this;
  context.req = request.req = response.req = req;
  context.res = request.res = response.res = res;
  request.ctx = response.ctx = context;
  request.response = response;
  response.request = request;
  context.originalUrl = request.originalUrl = req.url;
  context.state = {};
  return context;
}
複製代碼

上下文Context

Context屬性代理一些參數主要是經過delegates模塊實現的,這裏主要是以講述delegates爲主。express

//建立context的原型
const proto = module.exports = {
  ...
}

/** * 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');

複製代碼

Koacontext主要應用了delegates的三個方法,分別是methodaccessgetter方法。設計模式

初始化Context的時候,會在createContext中將responserequest賦值給ctx,所以context含有requestresponsekeyapi

//設置context的app、req、res、res、ctx
  context.app = request.app = response.app = this;
  context.req = request.req = response.req = req;
  context.res = request.res = response.res = res;
  request.ctx = response.ctx = context;
  request.response = response;
  response.request = request;

複製代碼

在建立context原型proto時候會調用delegator,將responserequestkey傳遞進去,再依次鏈式調用methodaccessgetter,將requestresponse中須要代理的屬性依次傳入。數組

如:當用戶經過調用ctx.set()時, 在此以前,在delegator中調用了method方法,已經將set傳遞進去,proto[name]能夠理解爲ctx['set'],賦值給proto[name]一個函數,因爲是ctx調用set,因此當前函數this的指向是ctx服務器

/** * 委託方法的名字 * * @param {String} name * @return {Delegator} self * @api public */
Delegator.prototype.method = function(name){
  // proto原型
  var proto = this.proto;
  //target 爲delegate的第二個參數,這裏是response | request
  var target = this.target;
  this.methods.push(name);

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

  return this;
};

複製代碼

而這個函數實際上就是經過將ctx.response.set經過apply進行調用,而後return出去的值。

/** * Delegator accessor `name`. * * @param {String} name * @return {Delegator} self * @api public */

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

/** * Delegator getter `name`. * * @param {String} name * @return {Delegator} self * @api public */

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;
};

/** * Delegator setter `name`. * * @param {String} name * @return {Delegator} self * @api public */

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方法是settergetting方法的連續調用,經過設置Object.__defineGetter__Object.__defineSetter__來進行數據劫持的。

因爲MDN並不推薦使用這種方法,所以這裏使用Object.defineProperty()從新寫gettersetter方法。

//getter
Delegator.prototype.getter = function(name){
  var proto = this.proto;
  var target = this.target;
  this.setters.push(name);
    
    Object.defineProperty(proto, name, {
      get: function() {
        return this[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: function(val) {
        return this[target][name] = val;
      }
    });

  return this;
};

複製代碼

最後

Koa的數據劫持主要是靠Object.__defineSetter__Object.__defineSetter__的應用,不過提及來,Koa總體的設計模式仍是很值得學習的。

相關文章
相關標籤/搜索