Node HTTP

Node HTTP is very simple. Each HTTP request has two objects: Request and Response.

const server = http.createServer((request, response) = > {
  // Request can get HTTP headers
  console.log(request.method);
  console.log(request.url);
  console.log(request.headers['cookie']);
  console.log(request.httpVersion);
  // response Response flow
  response.write('response data.');
  response.end();
});
server.listen(3000);
Copy the code

In addition, request inherits from a readable stream, so request has all the characteristics of a readable stream. For example, you can listen on(‘ data ‘) to get the content of the request body, or on(‘ end ‘) to listen for the end of the stream, with the following code:

const data = [];
  request.on('data'.function (dataBuffer{
    data.push(dataBuffer);
  });
  request.on('end'.function ({
    const buffer = Buffer.concat(data).toString();
    console.log(buffer);
  });
Copy the code

Response inherits from writable stream and has all the characteristics of writable stream, such as write, end, close operations. Response.wirte (‘ response data ‘) + Response.end () can respond data to client. Response.end (‘ XXX ‘) equals response.write(‘ XXX ‘) + response.close(); The code is as follows:

response.write('response data.');
  response.end();
Copy the code

Koa is based on Node HTTP

Koa is an HTTP framework, so koA is based on the Node HTTP module. The simplest implementation of KOA (which encapsulates HTTP internally) is as follows:

class Koa {
  constructor(fn) {
    this.fn = fn; } listen(... args) {const server = http.createServer((req, res) = > {
      this.fn(req, res);
    });
    return server.listen(...args);
  }
}
function handleHttp(req, res{
  res.end('response data.');
}
const koa = new Koa(handleHttp);
koa.listen(3000);
Copy the code

Koa’s Context

1. In the KOA Context, the following functions are implemented:

  • 1) Req and res attributes are defined to preserve the original Request and response objects, so we can access the original request and response objects through ctx.req and ctx.res.
  • 2) The request and Response attributes are defined to extend the native Request and Response objects.
  • Body = XXXX, which is response.body= XXX. When I access ctx.method, Request. Method is actually called.

2. The implementation principle of delegation is as follows:

function Delegator(proto, target{
  if(! (this instanceof Delegator)) { // This is to ensure that Delegator's instance comes out new
    return new Delegator(proto, target);
  }
  this.proto = proto;
  this.target = target;
}

Delegator.prototype.getter = function (name{
  const proto = this.proto;
  const target = this.target;
  proto.__defineGetter__(name, function ({
    return this[target][name]; Proto [target][name] proto[target]
  });
  return this;
};
Delegator.prototype.setter = function (name{
  const proto = this.proto;
  const target = this.target;
  proto.__defineSetter__(name, function (value{
    return this[target][name] = value; Proto [target][name] proto[target][name]
  });
  return this;
};
Delegator.prototype.access = function (name{
  return this.getter(name).setter(name);
};

Delegator.prototype.method = function (name{
  const proto = this.proto;
  const target = this.target;
  proto[name] = function ({
    Proto [target][name]();
    return this[target][name].apply(target, arguments);
  };
  return this;
};

const context = {
  request: {
    a123.fnfunction ({
      return 'fn'; }}}; Delegator(context,'request')
  .getter('a')
  .setter('a')
  .method('fn');

console.log(context.a); / / 123
console.log(context.fn()); // fn
Copy the code

The output is as follows:

123
fn
Copy the code

3. Application of Context in KOA

const request = {
  get method() {
    return this.req.method; }};const response = {
  get body() {
    return this._body;
  },
  set body(val) {
    this._body = val; }};const context = {};
Delegator(context, 'request').getter('method');
Delegator(context, 'response').access('body');

class Koa {
  constructor(fn) {
    this.fn = fn;
    this.context = Object.create(context);
    this.request = Object.create(request);
    this.response = Object.create(response); } listen(... args) {const server = http.createServer((req, res) = > {
      const context = this.createContext(req, res);
      Promise.resolve(this.fn(context)).then((a)= > {
        let body = 'koa response';
        if (context.body) {
          body = context.body;
        }
        res.write(body);
        res.end();
      });

    });
    returnserver.listen(... args); } createContext(req, res) {const context = Object.create(this.context);
    context.res = res;
    context.req = req;
    context.request = Object.create(this.request);
    context.response = Object.create(this.response);
    context.req = request.req = response.req = req;
    context.res = request.res = response.res = res;
    returncontext; }}function handleHttp(ctx{
  console.log(ctx.method); // ctx.method is delegated to ctx.request.method
  ctx.body = 'reponse data.'// ctx.body will be delegated to ctx.response.body
}
const koa = new Koa(handleHttp);
koa.listen(3000);
Copy the code

The Onion model

The core idea of KOA is the middleware based Onion model. Koa uses a compose function to compose one middleware piece by piece into a large function. Within each middleware piece, next() executes the next middleware piece. To quote an official picture of the onion model, see below:

The code for compose looks like this:

function compose(middlewares{
  return function (context, next{
    function dispatch(i{
      let fn = middlewares[i];
      if (i === middlewares.length) fn = next;
      if(! fn) {return Promise.resolve();
      }
      try {
        return Promise.resolve(fn(context, () => {
          return dispatch(i + 1);
        }));
      } catch (e) {
        return Promise.reject(e); }}return dispatch(0);
  };
}

const middlewares = [];
async function a(ctx, next{
  console.log('a1');
  const r = await next();
  console.log(r);
  console.log('a2');
}
async function b(ctx, next{
  console.log('b1');
  await next();
  console.log('b2');
  return 'b';
}
middlewares.push(a);
middlewares.push(b);
compose(middlewares)({}).then(v= > {
  console.log('ddd');
});
Copy the code

The above code is executed in the following order:

inin
b out
b
a out
end
Copy the code

5. Application of middleware onion model in KOA

const http = require('http');
const EventEmitter = require('events')

function compose(middlewares) {
  return function (context, next) {
    function dispatch(i) {
      let fn = middlewares[i];
      if (i === middlewares.length) fn = next;
      if(! fn) {return Promise.resolve();
      }
      try {
        return Promise.resolve(fn(context, () => {
          return dispatch(i + 1);
        }));
      } catch (e) {
        return Promise.reject(e); }}return dispatch(0);
  };
}

function Delegator(proto, target) {
  if(! (this instanceof Delegator)) { // This is to ensure that Delegator's instance comes out new
    return new Delegator(proto, target);
  }
  this.proto = proto;
  this.target = target;
}

Delegator.prototype.getter = function (name) {
  const proto = this.proto;
  const target = this.target;
  proto.__defineGetter__(name, function () {
    return this[target][name]; Proto [target][name] proto[target]
  });
  return this;
};
Delegator.prototype.setter = function (name) {
  const proto = this.proto;
  const target = this.target;
  proto.__defineSetter__(name, function (value) {
    return this[target][name] = value; Proto [target][name] proto[target][name]
  });
  return this;
};
Delegator.prototype.access = function (name) {
  return this.getter(name).setter(name);
};

Delegator.prototype.method = function (name) {
  const proto = this.proto;
  const target = this.target;
  proto[name] = function () {
    Proto [target][name]();
    return this[target][name].apply(target, arguments);
  };
  return this;
};


const request = {
  get method() {
    return this.req.method; }};const response = {
  get body() {
    return this._body;
  },
  set body(val) {
    this._body = val; }};const context = {};
Delegator(context, 'request').getter('method');
Delegator(context, 'response').access('body');

class Koa extends EventEmitter {
  constructor() {
    super(a);this.middlewares = [];
    this.context = Object.create(context);
    this.request = Object.create(request);
    this.response = Object.create(response); } listen(... args) {const server = http.createServer((req, res) = > {
      this.handleHttp(req, res);
    });
    returnserver.listen(... args); } use(fn) {this.middlewares.push(fn);
  }
  handleHttp(req, res) {
    const middleware = compose(this.middlewares);
    const context = this.createContext(req, res);
    middleware(context).then((a)= > {
      let body = 'koa response';
      if (context.body) {
        body = context.body;
      }
      res.write(body);
      res.end();
    });
  }
  createContext(req, res) {
    const context = Object.create(this.context);
    context.res = res;
    context.req = req;
    context.request = Object.create(this.request);
    context.response = Object.create(this.response);
    context.req = request.req = response.req = req;
    context.res = request.res = response.res = res;
    returncontext; }}const koa = new Koa();
async function middlewareA(ctx, next) {
  console.time('responseTime');
  console.log('middlewareA in');
  console.log(ctx.method); // ctx.method is delegated to ctx.request.method
  await next();
  console.log(ctx.body);
  console.log('middlewareA out');
  console.timeEnd('responseTime');
}
function middlewareB(ctx, next) {
  console.log('middlewareB in');
  ctx.res.setHeader('Content-Type'.'text/html; charset=utf-8');
  ctx.body = 'reponse data.'// ctx.body will be delegated to ctx.response.body
  console.log('middlewareB out');
}
koa.use(middlewareA);
koa.use(middlewareB);
const server = koa.listen(3000);
server.on('listening'.function () {
  console.log('listening on localhost:3000');
});
Copy the code

Put the code on Github

Github.com/SimpleCodeC…