美文网首页
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源码阅读-详解

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