The original

I feel that learning source code needs to be looked at with purpose in order to achieve the effect, but the company has no Node, no practice how to do? Recently found that debugging Koa2 source code is also a good way.

The preparatory work

- install node - install vscode - learn how to debug under vscodeCopy the code

You are advised to read node.js Debugging Guide for Node debugging.

We just need to learn how to debug in vscode. The specific details are not said, see the link, there are questions we can discuss.

Start with Hello World

Const Koa = require(const Koa = require('koa')
const app = new Koa()
const port = 3000

app.use(async (ctx, next) => {
    await next()
    ctx.response.status = 200
    ctx.response.body = 'hello world'
})

app.listen(port, () => {
    console.log(`server is running on the port: ${port}`)})Copy the code

Yes yes, through the above introductory code can also learn the source knowledge of Koa2.

First look at some of the apis used above. new Koa() app.use() app.listen()Copy the code

Let’s now go to node_modules and find Koa.

According to package.json, the entry file for Koa is

"main": "lib/application.js"
Copy the code
Js instance - context.js Context object - request.js request object - Response.js response objectCopy the code

Now of course we start with the entry file application.js. The first line of our example code is new koa(); We definitely have a class, and this is where we start.

/ / class constructor inherits the node EventEmitter / / http://nodejs.cn/api/events.html. The module exports = class Application extends Emitter {...  }Copy the code

Then we go to hit three breakpoints, which are as follows:

The three points of interruption correspond to the three apis that implement Koa2 mentioned earlier. Through these three breakpoints we step through how Koa2 is executed internally.

Constructor (); See the screenshot above for some notes.

this.middleware = []; This is used to store middleware registered with app.use().Copy the code

App.use ()

Fn clearly means:

async (ctx, next) => {
    await next()
    ctx.response.status = 200
    ctx.response.body = 'hello world'
}
Copy the code

The use(fn) method does the following:

2. Fn is not recommended to use generator functions, v2 version Koa2 will be converted, but v3 does not support generator functions, mainly for backward compatibility with KOA1. 3. Store the registered middleware 3.returnThis. Supports chained callsCopy the code

At this point you can see that this probably has these properties:

Application {
    _events:Object {}
    _eventsCount:0
    _maxListeners:undefined
    context:Object {}
    env:"development"
    middleware:Array(1) []
    proxy:false
    request:Object {}
    response:Object {}
    subdomainOffset:2
    Symbol(util.inspect.custom):inspect() {... } __proto__:EventEmitter }Copy the code

Then we go to listen(), where there’s this.callback(), and we need to go to the break point of this method to see what’s being executed.

Http.createserver (app.callback()).listen(...) Grammar sugar listen(... args) { debug('listen');
    const server = http.createServer(this.callback());
    returnserver.listen(... args); }Copy the code

// Callback () does the following: 1. Merge middleware with compose 2. Register listeners for error events for the application. 3. Return a request handler, handleRequestCopy the code

Next, let’s look at this.createcontext () and this.handlerequest (), interrupting to look at the code.

Note: A quick note, Node should have port occupancy issues all the time.Copy the code

A context object is created for each request.

handleRequest(ctx, fnMiddleware) { const res = ctx.res; res.statusCode = 404; // const onError = err => ctx.onerror(err); const handleResponse = () => respond(ctx); // Use the third-party library on-FINISHED to listen for the HTTP response and execute the callback when the request ends. The callback passed in here is context.onError (err), which is executed when an error occurs. onFinished(res, onerror); // All middleware execution (passing in the request context object CTX) is followed by the response handler (CTX), and onError (err) is used when exceptions are thrown.return fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }
Copy the code

Respond Interrupt point

/** * Response helper. * executes */ after all middleware execution is completefunctionRespond (CTX) {// allow bypassing KOa // Set ctx.respond =falseTo skip this function, but not recommendedif (false === ctx.respond) return; const res = ctx.res; // Exits this function when the context object is not writableif(! ctx.writable)return;

  letbody = ctx.body; const code = ctx.status; // ignore body // Empty the response body when the status code returned indicates that there is no response body:if (statuses.empty[code]) {
    // strip headers
    ctx.body = null;
    returnres.end(); } // If the request method is HEAD, check whether the response header is sent and whether the response body is in JSON format. If so, set the response content-length:if ('HEAD' == ctx.method) {
    if(! res.headersSent && isJSON(body)) { ctx.length = Buffer.byteLength(JSON.stringify(body)); }returnres.end(); } // status body // When the returned status code indicates that there is a response body, but the response body is empty, set the response body to the response information or status code. Set content-type and content-Length if the response header is not sent:if (null == body) {
    if (ctx.req.httpVersionMajor >= 2) {
      body = String(code);
    } else {
      body = ctx.message || String(code);
    }
    if(! res.headersSent) { ctx.type ='text';
      ctx.length = Buffer.byteLength(body);
    }
    returnres.end(body); } // Processing of different response subjects // responsesif (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
  body = JSON.stringify(body);
  if(! res.headersSent) { ctx.length = Buffer.byteLength(body); } res.end(body); }Copy the code

Error handling

Onerror (err) {// Raise an exception if err is not of the Error type.if(! (err instanceof Error)) throw new TypeError(util.format('non-error thrown: %j', err)); // When err. Status is 404 or Err. Expose istrueThe default error handler does not output an errorif (404 == err.status || err.expose) return; // By default, all errors are printed to stderr, unless app.silent istrue
    if (this.silent) return;

    const msg = err.stack || err.toString();
    console.error();
    console.error(msg.replace(/^/gm, ' '));
    console.error();
  }
Copy the code