Koa of source code analysis

1, Foreword

The main audience of this article is 1-3 years of junior and intermediate front-end engineers. I hope to study with you.

Why learn the source code?

After learning the daily development and application of Vue, React and Node, I seem to have encountered some bottlenecks and don't know how to go further.

There emerge in an endless stream of front-line learning routes on the Internet, and there are also tweets on official account, which are selling anxiety through the front-end learning route.

On the basis of combining many front-end routes, I slowly came up with my own idea: the front-end engineer first needs to be a software engineer. For the front-end coder who is not a computer major, are there some scenes you are familiar with, such as an experience you summarized or an obscure knowledge you analyzed for a long time in your daily work or in-depth study of front-end knowledge, In fact, it corresponds to an early theory in the computer field, that is, we need to "know what it is and why it is". After all, the development of front-end technology is based on computer. Secondly, we need to have our own in-depth exploration of the technical field, and the learning framework source code is to talk to the author and experience the ideas and logic of excellent technicians, which can improve the technical level faster.

Why Koa?

Looking at the source code is only a method and means, not an end. What we share this time is the interpretation of Koa source code.

In the process of Node learning, the koa framework occupies a pivotal part. As an HTTP middleware framework, it makes it easier to write Web applications and API s. This link is often mentioned in the interview, such as "do you understand the Node middleware mechanism?", "How is the koa onion model implemented?".

In terms of learning cost, Koa is different from large frameworks such as Vue and React, but a small framework with simplified code, simple overall implementation, cleverness and easy expansion.

Enterprise level Node framework and applied egg JS is also a Koa based package,

What is Koa? What are the components?

Koa It is a new web framework, built by the original team behind Express, and is committed to becoming a smaller, more expressive and more robust cornerstone in the field of web application and API development.

When we start from github Koa warehouse Clone the source code of the Koa framework. You can see from the lib file that only application js,context.js,request.js,response.js four parts, and Koa is composed of these four parts.

What is their connection?

  1. Through the package of Koa source code JSON file to get the entry file lib / Application JS, you can see Application JS is the Koa entry and Application library. It mounts context, request and response to the Application instance
  2. context.js is the upper and lower library
  3. request.js is the request library
  4. response.js is the response library

2, Martial arts script

1. Use framework

At the beginning of learning a new framework, the goal must be to be able to use the framework. At the beginning of learning Koa, I also asked myself to take this as the goal in a short time. I can choose the point of interest or through an application scenario. It is not recommended to learn directly along the official documents without the support of any point of interest or application scenario, Not only do you not realize the real advantages and disadvantages of Koa, but also you will gradually forget what you have learned over time. So I encourage you to learn with purpose when you learn something, so that you can learn more helpful knowledge in a relatively short time.

About how to use Koa, there are many articles on Nuggets at present. This article will not introduce it. You can also check my koa2 construction project To learn.

2. Understand the source code

When we use the framework, we often encounter many strange problems, and the official documents tend to prefer basic tutorials, and we also encounter the problem that we can't find the answer online.

Another is that when I use Vue, sometimes the Vue code written by the author is very different from that written by the boss. I often sigh, "it turns out that Vue can still be written like this", "I feel that I don't learn the same Vue as the boss", "can I really learn Vue?". The reason is that in addition to the rich basic knowledge of the boss, we must also be involved in the source code. When we encounter problems that cannot be solved, we also have a new idea - find answers in the source code.

Only with a solid grasp of Koa's source code can we be comfortable in the face of complex requirements.

3. Implementation framework

After reading the source code, we will have a good understanding of the framework coding style. At this time, we may think that the Koa framework is concise and ingenious. Can we implement a simple version of Koa by ourselves? Of course, we can better understand the ingenious design of this framework by implementing a simple version of Koa by ourselves

How to realize Koa? First of all, we need to find out the author's habitual methods, outline, find the entrance and draw the architecture diagram. Clarify the context of the whole framework through flow chart or architecture diagram, and then realize the functions one by one.

Of course, it can also be realized by "copying first, and then thinking of self created methods". After all, "the code in the world is copied greatly". Oh, the matter of literati is not called "copying", but "reference".

3, The second formula of Koa

Today we mainly learn the second form of Koa martial arts script - understanding the source code. Today we are mainly talking about the Koa2 version, which we passed Use frame Koa , you have basically mastered the simplest use:

const Koa = require("koa");
const app = new Koa();

app.use(async (ctx) => {
  ctx.body = "Hello World";
});

app.listen(3000);

Through this simple example, we try to analyze:

  • Based on the experience of front-end engineering, we can guess that Koa's entry file should be in package The main attribute of JSON is marked;
  • The Koa instance is created through the new keyword, that is, in the entry file, the default export is the Koa application class;
  • app is an instance of Koa, on which methods such as use and listen are mounted.

Let's look at the source code of Koa with conjectures. You can choose to go directly from github pull down koa framework source code , or Use frame Koa , the installation of Koa depends on the slave node_modules/koa / * * view

In the package of Koa project source code JSON file

{
  "main": "lib/application.js"
}

After verifying guess 1, let's continue to check lib / Application JS file, the default export is the Application application class, that is, the Koa class.

We found not only the listen and use methods, but also the oneror and callback methods in the Application class.

When we look at these methods, we find that each grammar is not very difficult, but it seems that we can't understand it when combined.

Because there will be some code robustness processing and some default value configuration in the source code of the Koa framework, which may increase the burden on learners, today we will only learn the more important parts.

1. application module

What does this entry file do?

  1. Build Application instance and koa portal
  2. Create an http service and call back when successful
  3. Callback function is a function passed in through use registration
  4. Mount the context, request and response to the Application instance
  5. When using koa, there will be ctx objects. Construct ctx objects yourself
  6. Pass ctx object to callback function
  1. introduce
// Judge whether it is a generator method
const isGeneratorFunction = require("is-generator-function");
// Set the anonymous space for debug
const debug = require("debug")("koa:application");
// A callback that is executed when the request is completed
const onFinished = require("on-finished");
// Introduce response
const response = require("./response");
// Middleware mechanism, onion peeling model
const compose = require("koa-compose");
// Introducing context
const context = require("./context");
// Introducing requiest
const request = require("./request");
// Toolkit for judging http status
const statuses = require("statuses");
// Node native event driven module
const Emitter = require("events");
// Node native toolkit module
const util = require("util");
// Node native Stream
const Stream = require("stream");
// Node native http module
const http = require("http");
// Returns the properties specified by the object
const only = require("only");
// Convert the middle price based on koa generator into Promise based Middleware
const convert = require("koa-convert");
// Give some information
const deprecate = require("depd")("koa");
// Module for creating Http Error
const { HttpError } = require("http-errors");
  1. Application class
module.exports = class Application extends Emitter {
  constructor(options) {
    // Call the parent class to construct
    super();
    // Set some initial values
    options = options || {};
    // Middleware queue array
    this.middleware = [];
    // Create a local context/request/response library and mount it on the koa instance
    this.context = Object.create(context);
    this.request = Object.create(request);
    this.response = Object.create(response);
    // Mount the debugged inspect method on custom
    if (util.inspect.custom) {
      this[util.inspect.custom] = this.inspect;
    }
  }
  // Listen to the port and start the service
  listen(...args) {
    debug("listen");
    // Use http module to create service, pass in callback function and listen port
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }
  // Returns the three properties on this object
  toJSON() {
    return only(this, ["subdomainOffset", "proxy", "env"]);
  }
  inspect() {
    return this.toJSON();
  }
  // Collect middleware and push it into middleware queue
  use(fn) {
    if (typeof fn !== "function")
      throw new TypeError("middleware must be a function!");
    // Judge whether it is the generators function. The v3 version will enable the generators function
    if (isGeneratorFunction(fn)) {
      deprecate(
        "Support for generators will be removed in v3. " +
          "See the documentation for examples of how to convert old middleware " +
          "https://github.com/koajs/koa/blob/master/docs/migration.md"
      );
      // Convert middleware into promise form
      fn = convert(fn);
    }
    debug("use %s", fn._name || fn.name || "-");
    this.middleware.push(fn);
    return this;
  }
  callback() {
    // Give the middleware queue to the onion module
    const fn = compose(this.middleware);

    if (!this.listenerCount("error")) this.on("error", this.onerror);
    // Process request
    const handleRequest = (req, res) => {
      // Building ctx objects
      const ctx = this.createContext(req, res);
      return this.handleRequest(ctx, fn);
    };

    return handleRequest;
  }
  // Used to process requests
  handleRequest(ctx, fnMiddleware) {
    // Obtain the native writable stream through the passed ctx
    const res = ctx.res;
    // Set the default statusCode 404
    res.statusCode = 404;
    const onerror = (err) => ctx.onerror(err);
    const handleResponse = () => respond(ctx);
    onFinished(res, onerror);
    return fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }
  // Build context object
  createContext(req, res) {
    // Separate instances are built for each request and response
    const context = Object.create(this.context);
    const request = (context.request = Object.create(this.request));
    const response = (context.response = Object.create(this.response));
    // Assign this to the app of context/request/response
    context.app = request.app = response.app = this;
    // Assign the incoming req to the req of context/request/response
    context.req = request.req = response.req = req;
    // Assign the incoming res to the res of context/request/response
    context.res = request.res = response.res = res;
    // Assign context to ctx of request/ response
    request.ctx = response.ctx = context;
    // Assign response to the response of request
    request.response = response;
    // Assign request to the request of response
    response.request = request;
    // Assign the url of req to the originalUrl of context and the originalUrl of request
    context.originalUrl = request.originalUrl = req.url;
    // Mount state
    context.state = {};
    return context;
  }
  // Handling exceptions
  onerror(err) {
    if (404 === err.status || err.expose) return;
    if (this.silent) return;

    const msg = err.stack || err.toString();
    console.error(`\n${msg.replace(/^/gm, "  ")}\n`);
  }
  static get default() {
    return Application;
  }
};
// Response request
function respond(ctx) {
  // allow bypassing koa by setting CTX Respond to bypass KOA
  if (false === ctx.respond) return;
  // Judge the writable attribute on the ctx prototype chain
  if (!ctx.writable) return;

  const res = ctx.res;
  let body = ctx.body;
  const code = ctx.status;

  // ignore body
  if (statuses.empty[code]) {
    // strip headers
    ctx.body = null;
    return res.end();
  }
  // body: json
  body = JSON.stringify(body);
  if (!res.headersSent) {
    ctx.length = Buffer.byteLength(body);
  }
  res.end(body);
}
// Mount http exception handling to koa's httprror
module.exports.HttpError = HttpError;
  1. application.js is the entrance of Koa, and the other three modules are also attached to their own instances.
  2. application.js exports the constructor inherited from the Emitter instance. The Koa framework has the ability of event monitoring and event triggering.
  3. The use method is mainly used to push the incoming function to the middleware queue and return the Koa instance to facilitate chain calling.
  4. The essence of the listen method is to http CreateServer is encapsulated, and at the same time, the callback method of Application class is called after creating the success. The middleware is merged in the callback method, the context is processed, and the Request and response are processed.
  5. As for the compose onion model, it is not in the Koa source code. This analysis will not look at the Koa compose first. It will be analyzed and implemented on the Koa source code implementation in the next article.

2. context module

  1. introduce
// Node native toolkit module
const util = require("util");
// http failure handling
const createError = require("http-errors");
const httpAssert = require("http-assert");
// Set agent library proxy
const delegate = require("delegates");
// http status Toolkit
const statuses = require("statuses");
// Operation cookie
const Cookies = require("cookies");
// It emphasizes uniqueness and can only be accessed in the current module, but not in other places
const COOKIES = Symbol("context#cookies");
  1. delegate principal agent
/**
 * Response delegation.
 */

delegate(proto, "response") // This allows you to access ctx ON ctx response
  .method("attachment")
  .method("redirect")
  .method("remove")
  .method("vary")
  .method("has")
  .method("set")
  .method("append")
  .method("flushHeaders")
  .access("status")
  .access("message")
  .access("body")
  .access("length")
  .access("type")
  .access("lastModified")
  .access("etag")
  .getter("headerSent")
  .getter("writable");

/**
 * Request delegation.
 */

delegate(proto, "request") // This allows you to access ctx ON ctx request
  .method("acceptsLanguages")
  .method("acceptsEncodings")
  .method("acceptsCharsets")
  .method("accepts")
  .method("get")
  .method("is")
  .access("querystring")
  .access("idempotent")
  .access("socket")
  .access("search")
  .access("method")
  .access("query")
  .access("path")
  .access("url")
  .access("accept")
  .getter("origin")
  .getter("href")
  .getter("subdomains")
  .getter("protocol")
  .getter("host")
  .getter("hostname")
  .getter("URL")
  .getter("header")
  .getter("headers")
  .getter("secure")
  .getter("stale")
  .getter("fresh")
  .getter("ips")
  .getter("ip");
  1. You can use node_modules find the delegate library, you can see
  • The method function is a registration function
  • The getter function is a registered read-only property
  • The access function registers readable and writable properties
  1. The main function of ctx is the function of proxy request and response, which provides convenient access to request and response objects. For example, we are going to visit ctx response. Status, but through delegate, we can directly access ctx Status to access it.

3. request module

  1. The request module abstracts and encapsulates the request object, and handles some attributes in a special way.
  2. The request module is relatively simple and has a large amount of code, so it will not be interpreted too much.

3. response module

  1. The response module abstracts and encapsulates the response object, and handles some attributes in a special way.
  2. The response module is relatively simple and has a large amount of code, so it will not be interpreted too much.

4, Summary

This paper describes why to learn the source code, how to learn the source code and how to learn a new framework.

Koa is just a framework. The learning methods in this article can be applied to many frameworks. Of course, you also have better learning methods. You are also welcome to speak out from the comment area and grow together.

At the same time, this article is the second and third part of the Koa series, which is on the way to realize the source code of Koa. Welcome to praise, pay attention to and support a wave. thank!!!

I'm from the front stream. Welcome interested students to pay attention Front end Creek official account Welcome to wechat wxl-15153496335.

Keywords: Javascript node.js Front-end Interview

Added by unknown on Tue, 01 Mar 2022 07:24:26 +0200