Koa source reading - details

Carry on the previous chapter

Application function

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 and response are empty objects. They can access the properties and methods on the corresponding prototype objects
        this.context = Object.create(context);
        this.request = Object.create(request);
        this.response = Object.create(response);
    }
}

Emitter

Application inherited from twitter

The main purpose of Twitter is to observe the subscriber mode. You can write this in index.js, and the result will appear

const app = new Koa();

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

Learn about 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 in b's prototype chain, b can access a's properties

unit testing

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

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

The only function returns the new object and only the key. The value of the key is not 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;
  }, {});
};
//Execute app.inspect() 
describe('app.inspect()', () => {
  it('should work', () => {
    const app = new Koa();
    const util = require('util');
    const str = util.inspect(app);
    // Pass if equal
    assert.equal("{ subdomainOffset: 2, proxy: false, env: 'test' }", str);
  });
});

Here's a question, why are there only three attributes for the object after the index.js instance???

listen

Call listen to perform 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;
  }

By the way, this is how a native http creates a service

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

createContext

createContext(req, res) {
    // Create the object based on this.context. At this time, the introduced this.context has been instantiated twice. request and response are the same, and are hung on the context as attributes
    const context = Object.create(this.context);
    const request = context.request = Object.create(this.request);
    const response = context.response = Object.create(this.response);
    // this,req,res can be obtained from context request response
    context.app = request.app = response.app = this;
    context.req = request.req = response.req = req;
    context.res = request.res = response.res = res;
    // The request response has a context;
    request.ctx = response.ctx = context;
    // request and response can get each other's objects
    request.response = response;
    response.request = request;
    // The originalUrl of context and request has a request.url
    context.originalUrl = request.originalUrl = req.url;
    // context can get cookie s
    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 = {};
    // Return to context
    return context;
}

respond

After middleware processing, respond

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

  const res = ctx.res;
  if (!ctx.writable) return;
  //Get body
  let body = ctx.body;
  const code = ctx.status;
  // If the body does not exist, the default not found is returned
  // 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);
  }
  // If the body is binary, string, Stream stream, return directly
  // 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: handling of JSON
  body = JSON.stringify(body);
  if (!res.headersSent) {
    ctx.length = Buffer.byteLength(body);
  }
  res.end(body);
}

context function

In the previous section, we mainly dealt with onerror and exposed attributes
Look at the most important

/**
* context The method of attachment redirect under response can be used, 
* It can set the property of status message under respons es, 
* Can read the values of headerSent and 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) {
  // The calling function is the self after returning the instance. Each time it is called, it is different from this. It is good to call the methods on the prototype object
  if (!(this instanceof Delegator)) return new Delegator(proto, target);
  this.proto = proto;
  this.target = target;
  this.methods = [];
  this.getters = [];
  this.setters = [];
  this.fluents = [];
}
// Mount the method on target on proto, while the execution of this is target's
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] can read 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] can set 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;
};

Readable and writable

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

request object

In a word, the request object mainly gets the requested information
Let's look at some examples

// Return this.req.headers information
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 the content of the request header to detoxify
get length() {
    const len = this.get('Content-Length');
    if (len == '') return;
    return ~~len;
},
//Get query information
get querystring() {
    if (!this.req) return '';
    return parse(this.req).query || '';
}

response object

The response object mainly contains the information of the corresponding header

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

Combination Official website Look better

Thank you for your reading. Your praise is my motivation to write this article.

git blog address

Tags: node.js JSON socket git

Posted on Sun, 01 Dec 2019 23:37:22 -0800 by s0me0ne