Koa源码阅读-详解

承接上一章

Application函数

module.exports = class Application extends Emitter {
    constructor() {
        super();
    
        this.proxy = false;
        this.middleware = [];
        this.subdomainOffset = 2;
        this.env = process.env.NODE_ENV || 'development';
        // context,request,response是空对象,能访问相应原型对象上的属性和方法
        this.context = Object.create(context);
        this.request = Object.create(request);
        this.response = Object.create(response);
    }
}

Emitter

Application继承自Emitter

Emitter主要是观察订阅者模式,在index.js可以这样写,会出现结果

const app = new Koa();

app.on('a',(element)=>{
  // 111
  console.log(element)
})
app.emit('a',111)

学习一下Object.create

const a = {a:1,b:2}
const b = Object.create(a); // {}
console.log(b.__proto__ === a) //true
console.log(b.a) //1

a在b的原型链上,b能访问a的属性

单元测试

toJSON() {
    return only(this, [
      'subdomainOffset',
      'proxy',
      'env'
    ]);
}

inspect() {
    return this.toJSON();
}

only函数返回新对象,并且只返回包含key,key的值不为null

module.exports = function(obj, keys){
  obj = obj || {};
  if ('string' == typeof keys) keys = keys.split(/ +/);
  return keys.reduce(function(ret, key){
    if (null == obj[key]) return ret;
    ret[key] = obj[key];
    return ret;
  }, {});
};
//执行app.inspect() 
describe('app.inspect()', () => {
  it('should work', () => {
    const app = new Koa();
    const util = require('util');
    const str = util.inspect(app);
    // 相等则通过
    assert.equal("{ subdomainOffset: 2, proxy: false, env: 'test' }", str);
  });
});

这里有个疑问,为什么在index.js 实例后的对象也只有这3个属性???

listen

调用listen执行callback

  listen() {
    debug('listen');
    const server = http.createServer(this.callback());
    return server.listen.apply(server, arguments);
  }
  callback() {
    const fn = compose(this.middleware);
    if (!this.listeners('error').length) this.on('error', this.onerror);

    const handleRequest = (req, res) => {
      res.statusCode = 404;
      const ctx = this.createContext(req, res);
      const onerror = err => ctx.onerror(err);
      const handleResponse = () => respond(ctx);
      onFinished(res, onerror);
      fn(ctx).then(handleResponse).catch(onerror);
    };

    return handleRequest;
  }

在这里顺便说一下,原生 http创建一个服务是这样子滴

const http = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('okay');
});
http.listen(8000)

createContext

createContext(req, res) {
    // 以this.context为蓝本建立对象,此时引入的this.context已经两次实例了。request和response同理,并且作为属性挂在context上
    const context = Object.create(this.context);
    const request = context.request = Object.create(this.request);
    const response = context.response = Object.create(this.response);
    // context  request response都能拿到this,req,res
    context.app = request.app = response.app = this;
    context.req = request.req = response.req = req;
    context.res = request.res = response.res = res;
    // request response有context;
    request.ctx = response.ctx = context;
    // request和response能互相拿到各自的对象
    request.response = response;
    response.request = request;
    // context 和 request的originalUrl有了req.url
    context.originalUrl = request.originalUrl = req.url;
    // context能拿到cookie
    context.cookies = new Cookies(req, res, {
      keys: this.keys,
      secure: request.secure
    });
    request.ip = request.ips[0] || req.socket.remoteAddress || '';
    context.accept = request.accept = accepts(req);
    context.state = {};
    // 返回 context
    return context;
}

respond

经过中间件的处理,respond

function respond(ctx) {
  // allow bypassing koa
  if (false === ctx.respond) return;

  const res = ctx.res;
  if (!ctx.writable) return;
  //拿到body
  let body = ctx.body;
  const code = ctx.status;
  // 如果body不存在,那么返回默认not found
  // status body
  if (null == body) {
    body = ctx.message || String(code);
    if (!res.headersSent) {
      ctx.type = 'text';
      ctx.length = Buffer.byteLength(body);
    }
    return res.end(body);
  }
  // 如果body是二进制,是字符串,Stream流,直接返回
  // responses
  if (Buffer.isBuffer(body)) return res.end(body);
  if ('string' == typeof body) return res.end(body);
  if (body instanceof Stream) return body.pipe(res);

  // body: json  json的处理
  body = JSON.stringify(body);
  if (!res.headersSent) {
    ctx.length = Buffer.byteLength(body);
  }
  res.end(body);
}

context函数

前面主要做了onerror和暴露属性的处理 来看最重要的

/**
* context能够使用response下attachment redirect的方法, 
* 能够设置respons下status message的属性, 
* 能读headerSent和writable的值
*/
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');
function Delegator(proto, target) {
  // 调用函数即返回实例后的自己,每调用一次都是不同的this,好处能够调用原型对象上的方法
  if (!(this instanceof Delegator)) return new Delegator(proto, target);
  this.proto = proto;
  this.target = target;
  this.methods = [];
  this.getters = [];
  this.setters = [];
  this.fluents = [];
}
// 把target的上的方法挂载在proto上,而执行的this却是target的
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;
};

proto[name]能读取target[name]

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

proto[name]能设置target[name]

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

可读可写

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

request对象

一句话,request对象主要做了拿到请求的信息 我们看几个例子

// 返回this.req.headers信息
get header() {
    return this.req.headers;
},

get(field) {
    const req = this.req;
    switch (field = field.toLowerCase()) {
      case 'referer':
      case 'referrer':
        return req.headers.referrer || req.headers.referer || '';
      default:
        return req.headers[field] || '';
}
},
// 获取请求头的内容铲毒
get length() {
    const len = this.get('Content-Length');
    if (len == '') return;
    return ~~len;
},
//获得query信息
get querystring() {
    if (!this.req) return '';
    return parse(this.req).query || '';
}

response对象

response对象主要做了相应头的的信息

set(field, val) {
    if (2 == arguments.length) {
      if (Array.isArray(val)) val = val.map(String);
      else val = String(val);
      this.res.setHeader(field, val);
    } else {
      for (const key in field) {
        this.set(key, field[key]);
      }
    }
},
set lastModified(val) {
    if ('string' == typeof val) val = new Date(val);
    this.set('Last-Modified', val.toUTCString());
    },
    set etag(val) {
    if (!/^(W\/)?"/.test(val)) val = `"${val}"`;
    this.set('ETag', val);
}

redirect(url, alt) {
    // location
    if ('back' == url) url = this.ctx.get('Referrer') || alt || '/';
    this.set('Location', url);
    
    // status
    if (!statuses.redirect[this.status]) this.status = 302;
    
    // html
    if (this.ctx.accepts('html')) {
      url = escape(url);
      this.type = 'text/html; charset=utf-8';
      this.body = `Redirecting to <a href="${url}">${url}</a>.`;
      return;
    }
    
    // text
    this.type = 'text/plain; charset=utf-8';
    this.body = `Redirecting to ${url}.`;
}

结合官网看更好

以上,🌹谢谢你的阅读,你的点赞是我写文章的动力。

git博客地址