美文网首页
Koa源码阅读-详解

Koa源码阅读-详解

作者: 织雪纱奈 | 来源:发表于2019-03-18 16:37 被阅读0次

承接上一章

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

结合官网看更好

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

相关文章

  • Koa源码阅读-详解

    承接上一章 Application函数 Emitter Application继承自Emitter Emitter...

  • Koa源码阅读

    Koa源码 继着上文的Co源码以及与Koa的深入理解,开始学习下Koa是怎么写的。我看的版本是1.1.2的。 从p...

  • 十分钟带你看完 KOA 源码

    前段时间看了 koa 源码,得益于 koa 良好抽象,不仅提供了简洁的 api ,同时也使得源码相当的简洁和优雅。...

  • koa-compose源码阅读

    众所周知,在函数式编程中,compose是将多个函数合并成一个函数(形如: g() + h() => g(h())...

  • iOS AFNetWorking源码详解(一)

    原文地址: iOS AFNetWorking源码详解(一) iOS AFNetWorking源码详解(二) iOS...

  • MapReduce之InputFormat数据输入

    1.Job提交流程和切片源码详解 (1).job提交流程源码详解 (2).FileInputFormat源码解析(...

  • koa源码

    koa 源码学习记录。因为代码量非常少,所以也很好理解。 http.createServer([requestLi...

  • koa连接MySQL

    这个教程不管node,express,koa都可以用下面方法连接,这里用koa做个参考 源码地址:https://...

  • koa2源码解析

    Koa源码解析 整体架构 核心文件只有4个,在lib文件夹下: application.js koa框架的入口...

  • koa连接mysql

    这个教程不管node,express,koa都可以用下面方法连接,这里用koa做个参考 源码地址:https://...

网友评论

      本文标题:Koa源码阅读-详解

      本文链接:https://www.haomeiwen.com/subject/jcrbmqtx.html