Koa上下文Context的数据劫持

这几天我一直在思索,关于Koa我可以分享什么?
分享Koa中间件实现原理?
上一篇的《从源码上理解express中间件》已经解释的比较完善了,Koa中间件实现也比较相似,只不过,是把中间件函数抽离出来为koa-compose,循环遍历的时候将函数外边套了tryCatch,里面加了Promise.resolve(fn)而已。

因此,这篇文章主要分享下Koa的数据劫持。

如: 以下访问器和 Request 别名等效。


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()创建一个新对象,带着指定的原型对象和属性
点击这里查看MDN

  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()

//启动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为主。

//创建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含有requestresponsekey

  //设置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整体的设计模式还是很值得学习的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值