Koa principle implementation

Zero, Koa source directory structure

. ├ ─ ─ the History. The md ├ ─ ─ LICENSE ├ ─ ─ the Readme. Md ├ ─ ─ dist │ └ ─ ─ koa. MJS ├ ─ ─ lib │ ├ ─ ─ application. Js # at the core of module │ ├ ─ ─ the context, js ├─ ├─ ├─ download.json # ├─ ├─ download.json # ├─ ├─ ├─ download.jsonCopy the code

1. Basic structure

Use:

const Koa = require('./koa')

const app = new Koa()

app.listen(3000)
Copy the code

application.js

module.exports = class Application {
  listen(. args) {
    const server = http.createServer((req, res) = > {
      res.end('My Koa')})returnserver.listen(... args) } }Copy the code

Second, realize the middleware function

  • Koa will combine all the middleware into one big Promise

  • When this Promise completes, the current CTx.body is used for the result response

  • Next must be preceded by await or return next, otherwise the execution order will not be as expected

  • If it is all synchronous, it doesn’t matter whether we await it or not

  • I don’t know if there will be asynchronous logic, so I recommend writing with “await next”

Collecting middleware

Usage:

const Koa = require('./koa')

const app = new Koa()

app.use((ctx, next) = > {
  ctx.body = 'foo'
})

app.use((ctx, next) = > {
  ctx.body = 'Koa'
})

app.listen(3000)
Copy the code

application.js

const http = require("http");

module.exports = class Application {
  constructor() {
    this.middleware = [];
  }

  use(fn) {
    if (typeoffn ! = ="function") {
      throw new TypeError("middleware must be a function!");
    }
    this.middleware.push(fn);
  }

  listen(. args) {
    const server = http.createServer((req, res) = > {
      res.end("My Koa");
    });
    return server.listen(...args);
  }
};

Copy the code
const http = require("http");

class Application {
  constructor() {
    this.middleware = []; // Save the middleware functions added by the user
  }

  listen(. args) {
    const server = http.createServer(this.callback()); server.listen(... args); }use(fn) {
    this.middleware.push(fn);
  }

  // Asynchronous recursive traversal calls middleware handler functions
  compose(middleware) {
    return function () {
      const dispatch = (index) = > {
        if (index >= middleware.length) return Promise.resolve();
          
        const fn = middleware[index];
        return Promise.resolve(
          // TODO:Context object
          fn({}, () = > dispatch(index + 1)) // This is the next function
        );
      };

      // Returns the first middleware handler
      return dispatch(0);
    };
  }

  callback() {
    const fnMiddleware = this.compose(this.middleware);
    const handleRequest = (req, res) = > {
      fnMiddleware()
        .then(() = > {
          console.log("end");
          res.end("My Koa");
        })
        .catch((err) = > {
          res.end(err.message);
        });
    };

    returnhandleRequest; }}module.exports = Application;
Copy the code

Test the middleware execution process

/** * - Koa middleware features * + collect when use * + call when request comes in */

const Koa = require("./koa");

const app = new Koa();

const one = (ctx, next) = > {
  console.log(">> one");
  next();
  console.log("<< one");
};

const two = (ctx, next) = > {
  console.log(">> two");
  next();
  console.log("<< two");
};

const three = (ctx, next) = > {
  console.log(">> three");
  next();
  console.log("<< three");
};

app.use(one);
app.use(two);
app.use(three);

// console.log(app.middleware)

app.listen(3000);
Copy the code

3. Handle context objects

Initialize the context object

Context How context objects can be used:

/** * Koa Context */

const Koa = require("./koa");

const app = new Koa();

app.use(async (ctx, next) => {
  // Koa Context encapsulates node's request and response objects into a single object, providing many useful methods for writing Web applications and apis.
  // Each request creates a Context and is referenced as a parameter in the middleware
  console.log(ctx); / / the Context object
  console.log(ctx.req.url);
  console.log(ctx.req.method);
  console.log(ctx.request.req.url);
  console.log(ctx.request.req.method);

  console.log(ctx.req); // Node request object
  console.log(ctx.res); // Node's response object
  console.log(ctx.req.url);

  console.log(ctx.request); // The request object encapsulated in Koa
  console.log(ctx.request.header); // Get the request header object
  console.log(ctx.request.method); // Get the request method
  console.log(ctx.request.url); // Get the request path
  console.log(ctx.request.path); // Get the request path that does not contain the query string
  console.log(ctx.request.query); // Get the query string in the request path

  / / Request alias
  // See https://koa.bootcss.com/#request- for a complete list
  console.log(ctx.header);
  console.log(ctx.method);
  console.log(ctx.url);
  console.log(ctx.path);
  console.log(ctx.query);

  // The response object encapsulated in Koa
  console.log(ctx.response);
  ctx.response.status = 200;
  ctx.response.message = "Success";
  ctx.response.type = "plain";
  ctx.response.body = "Hello Koa";

  / / Response alias
  ctx.status = 200;
  ctx.message = "Success";
  ctx.type = "plain";

  ctx.body = "Hello Koa";
});

app.listen(3000);
Copy the code

context.js

const context = {
  
}

module.exports = context
Copy the code

request.js

const url = require("url");

const request = {
  get method() {
    return this.req.method;
  },

  get header() {
    return this.req.headers;
  },

  get url() {
    return this.req.url;
  },

  get path() {
    return url.parse(this.req.url).pathname;
  },

  get query() {
    return url.parse(this.req.url, true).query; }};module.exports = request;
Copy the code

response.js

const response = {
  set status(value) {
    this.res.statusCode = value; }};module.exports = response;
Copy the code

application.js

const http = require("http");
const context = require("./context");
const request = require("./request");
const response = require("./response");

class Application {
  constructor() {
    this.middleware = []; // Save the middleware functions added by the user

    this.context = Object.create(context);
    this.request = Object.create(request);
    this.response = Object.create(response);
  }

  listen(. args) {
    const server = http.createServer(this.callback()); server.listen(... args); }use(fn) {
    this.middleware.push(fn);
  }

  // Asynchronous recursive traversal calls middleware handler functions
  compose(middleware) {
    return function (context) {
      const dispatch = (index) = > {
        if (index >= middleware.length) return Promise.resolve();
        const fn = middleware[index];
        return Promise.resolve(
          // TODO:Context object
          fn(context, () = > dispatch(index + 1)) // This is the next function
        );
      };

      // Returns the first middleware handler
      return dispatch(0);
    };
  }

  // Construct the context object
  createContext(req, res) {
    // An instance can handle multiple requests, and different requests should have different context objects. To avoid cross-contamination of data during requests, a new copy of this data is made here
    const context = Object.create(this.context);
    const request = (context.request = Object.create(this.request));
    const response = (context.response = Object.create(this.response));

    context.app = request.app = response.app = this;
    context.req = request.req = response.req = req;
    context.res = request.res = response.res = res;
    request.ctx = response.ctx = context;
    request.response = response;
    response.request = request;
    context.originalUrl = request.originalUrl = req.url;
    context.state = {};
    return context;
  }

  callback() {
    const fnMiddleware = this.compose(this.middleware);
    const handleRequest = (req, res) = > {
      // Each request creates a separate Context object that does not pollute each other
      const context = this.createContext(req, res);
      fnMiddleware(context)
        .then(() = > {
          res.end("My Koa");
        })
        .catch((err) = > {
          res.end(err.message);
        });
    };

    returnhandleRequest; }}module.exports = Application;
Copy the code

Handles aliases in context objects

context.js

const context = {
  // get method () {
  // return this.request.method
  // },
  // get url () {
  // return this.request.url
  // }
};

defineProperty("request"."method");
defineProperty("request"."url");

function defineProperty(target, name) {
  context.__defineGetter__(name, function () {
    return this[target][name];
  });
  // Object.defineProperty(context, name, {
  // get () {
  // return this[target][name]
  / /}
  // })
}

module.exports = context;
Copy the code

4. Handle CTx. body

Save body data

response.js

const response = {
  set status(value) {
    this.res.statusCode = value;
  },

  _body: "".// It is used to store data

  get body() {
    return this._body;
  },

  set body(value) {
    this._body = value; }};module.exports = response;
Copy the code

Sending body data

context.js

const context = {
  // get method () {
  // return this.request.method
  // },
  // get url () {
  // return this.request.url
  // }
};

defineProperty("request"."method");
defineProperty("request"."url");
defineProperty("response"."body");

function defineProperty(target, name) {
  // context.__defineGetter__(name, function () {
  // return this[target][name]
  // })
  Object.defineProperty(context, name, {
    get() {
      return this[target][name];
    },

    set(value) {
      this[target][name] = value; }}); }module.exports = context;
Copy the code

application.js

callback() {
    const fnMiddleware = this.compose(this.middleware);
    const handleRequest = (req, res) = > {
      // Each request creates a separate Context object that does not pollute each other
      const context = this.createContext(req, res);
      fnMiddleware(context)
        .then(() = > {
          res.end(context.body)
          // res.end('My Koa')
        })
        .catch((err) = > {
          res.end(err.message);
        });
    };

    return handleRequest;
  }
Copy the code

More flexible body data

callback() {
    const fnMiddleware = this.compose(this.middleware);
    const handleRequest = (req, res) = > {
      // Each request creates a separate Context object that does not pollute each other
      const context = this.createContext(req, res);
      fnMiddleware(context)
        .then(() = > {
          respond(context);
          // res.end(context.body)
          // res.end('My Koa')
        })
        .catch((err) = > {
          res.end(err.message);
        });
    };

    return handleRequest;
  }



function respond(ctx) {
  const body = ctx.body;
  const res = ctx.res;

  if (body === null) {
    res.statusCode = 204;
    return res.end();
  }

  if (typeof body === "string") return res.end(body);
  if (Buffer.isBuffer(body)) return res.end(body);
  if (body instanceof Stream) return body.pipe(ctx.res);
  if (typeof body === "number") return res.end(body + "");
  if (typeof body === "object") {
    const jsonStr = JSON.stringify(body);
    returnres.end(jsonStr); }}Copy the code