preface
The main content of this article is to learn koA’s source code with others by describing the author’s own process of learning koA’s source code. Koa was designed to be a smaller, more expressive, and more robust cornerstone of Web application and API development. Because of its high scalability, koA’s source code is relatively easy to read. If you follow my article to learn, it must be my bad writing.
In the last article, we learned about directory structures and the entry file application.js. In this article, we will learn about them together.
Application
When we open lib/appication, we find that this file is only 200 lines long. How do we read this file? Let’s start with the Koa project –hello Word launch
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello World';
}); app.listen(3000); Copy the code
This basic project did four things
- Require Koa, which imports our application.js
- Create an instance of the class exported by our Application
- The instance’s use method is called, passing in a function
- The listen method is called, listening on port 3000
Let’s look at the contents of our application.js in the following way. (version [email protected])
Look at the exported content first
See line 30 is exportedThe application classAnd the class inheritsEmitter“And take a lookEmitterIt’s through line 16eventsImported by the module.The events module is not found in node_modules, indicating that the Events module is a native module of Node. The application class inherits node’s native EVetns module. Take a look at the events module.
constructor
Now that you know the inheritance of the application, proceed to the initialization of the application. I have added a few comments of my own to the code below, which will be explained below.
constructor(options) {
super();
// ① Configuration item information is related options = options || {};
this.proxy = options.proxy || false; this.subdomainOffset = options.subdomainOffset || 2; this.proxyIpHeader = options.proxyIpHeader || 'X-Forwarded-For'; this.maxIpsCount = options.maxIpsCount || 0; this.env = options.env || process.env.NODE_ENV || 'development'; if (options.keys) this.keys = options.keys; // important attributes this.middleware = []; this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); // check the correlation if (util.inspect.custom) { this[util.inspect.custom] = this.inspect; } } Copy the code
I’ve divided the constructor code above into roughly three pieces
- ① The related part of the configuration item option controls some of its own attributes through the parameters passed in the new. By name, you can roughly guess what the meaning is, but it is not clear what it is for, and do not know under what circumstances it will be used, so do not tangle. It’s nice to know that there are some properties that are defined here and give some default values, but you can look at them when you use them.
- ② Important attributes
If you have used KOA, then you can guess roughly what these attributes are- Middleware KOa is the foundation for middleware to work with the Onion model
- contextContext object, through
const context = require('./context');
So let’s introducelib/context.js - Request The request object of KOA is lib/request.js
- Response Koa’s response object is lib/response.js
- ③ Check the relevant also do not tangle to use again
use
After we new an application object, we start using the method that calls application, app.use
<! --const Koa = require('koa'); --><! --const app = new Koa(); -->
// Look at this sentenceapp.use(async ctx => {
ctx.body = 'Hello World'; }); <! --app.listen(3000); -->Copy the code
Take a look at the source code for the use method, at line 122 in application.js.
use(fn) {
// Ensure that the parameter must be onefunction
if(typeof fn ! = ='function') throw new TypeError('middleware must be a function! ');
// If it is a generator function, perform a conversion 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'); fn = convert(fn); } // ③ Output in debug state debug('use %s', fn._name || fn.name || The '-'); // ④ Push the method to our middleware array this.middleware.push(fn); If you want a chain call, you must return itself return this; } Copy the code
What this method does is very simple
- ① Ensure that the parameter must be a function
- ② If it is a generator function, perform a conversion
- IsGeneratorFunction isGeneratorFunction is a generator function, but I don’t care how it is implemented
- The v3 version no longer supports gennorator as a parameter, while telling you how to convert
- The convert method is the introduced KOA-convert package, which simply converts Generator functions into async functions. More specifically, convert the Generator function into a Promise object wrapped in CO.
- We see that many projects have debug mode to expose the execution information, using a similar method, does not affect the main process of the in-depth look.
- Use calls push methods into the Middleware array. In essence, app.use calls push parameters (methods) into the Middleware array.
- ⑤ If you want a chain call, you must return itself
listen
We continue to execute our KOA procedures
<! --const Koa = require('koa'); --><! --const app = new Koa(); -->
<! --app.use(async ctx => {--><! -- ctx.body ='Hello World'; --><! -}); --> app.listen(3000); // Look at this sentenceCopy the code
Take a look at the listen function, at line 79 of application.js
listen(... args) { debug('listen');
// ① Invoke the createServer method of the HTTP module in Node const server = http.createServer(this.callback());
// the http.Server instance calls listen returnserver.listen(... args);} Copy the code
Listen encapsulates the creation and listening services of Node’s native HTTP module
- This section describes how the HTTP createServer method is used and how it returns an HTTP. Server instance
- The http.Server instance calls the listen method, which can be seen in the documentation of server.listen.
callback
Koa’s LISTEN method calls the callback method, so let’s see what the callback method does. The code is in line 143 of application.js
callback() {
// ① The onion model is the core principle const fn = compose(this.middleware);
// error listening is related if(! this.listenerCount('error')) this.on('error', this.onerror); // ③ KoA-wrapped requestListener const handleRequest = (req, res) => { const ctx = this.createContext(req, res); return this.handleRequest(ctx, fn); }; return handleRequest; } Copy the code
This callback method is at the heart of koA’s event handling logic
- ① The core principle of compose Onion model
So what is the Onion model
const Koa = require('koa');
let app = new Koa();
const middleware1 = async (ctx, next) => {
console.log(1);
await next(); console.log(6); } const middleware2 = async (ctx, next) => { console.log(2); await next(); console.log(5); } const middleware3 = async (ctx, next) => { console.log(3); await next(); console.log(4); } app.use(middleware1); app.use(middleware2); app.use(middleware3); app.use(async(ctx, next) => { ctx.body = 'hello world' }) app.listen(3001) // output 1,2,3,4,5,6Copy the code
We know that by analyzing the previous codeapp.use(fn)The main function of the delta function is to evaluatefnforthis.middleware.push(fn)In the code above we passapp.use(middleware1); app.use(middleware2); app.use(middleware3);
将this.middlewareThe array becomes[middleware1, middleware1, middleware1]
The callback functionThe first sentence is executedconst fn = compose(this.middleware);
Find the definition of composeTo find thekoa-composeThe source code is as follows (some will not affect the main process content deletion) :
function compose (middleware) {
return function (context, next) {
// last called middleware #
let index = -1
return dispatch(0)
function dispatch (i) { if (i <= index) return Promise.reject(new Error('next() called multiple times')) index = i let fn = middleware[i] if (i === middleware.length) fn = next if(! fn)return Promise.resolve() try { return Promise.resolve(fn(context, function next () { return dispatch(i + 1) })) } catch (err) { return Promise.reject(err) } } } } Copy the code
The code is a few dozen lines short and the key is the dispatch function, which traverses the middleware and passes the context and Dispatch (I + 1) to the middleware methods. It’s done neatly
- will
context
All the way down to the middleware - will
middleware
The next middleware infn
As to the futurenext
The return value of the
Compose transforms [MIDDLEware1, middleware1, middleware1] into something like middleware1(Middleware2 (Middleware3 ())). The function. The onion model is formed by using the compose feature and the waiting execution of next in async await. We can use this feature to process the request before next and the response after next.
- This. (2) errors to monitor related call listenerCount function, I find the application. The js code no correlation function, think of application inherit from events through the node API, The listenerCount method finds the main functions as follows
This line of code is very clear: it checks whether an error message has been listened for, and if not, it passes the error messageonerrorMethod wraps the error message back.
- ③ KoA-wrapped requestListener, which defines a handleRequest function and returns it
This.callback() is used as a parameter to http.createserver. It is clear that the req and RES are the reQ and RES objects returned by http.createserve in Node. We create a context object, CTX, using the this.createContext function. The this.handleRequest function (which is not the same as function handleRequest) is returned, taking CTX and middleware converted by compose as arguments.
createContext
Let’s first look at how the this.createContext function creates a context object in line 177 of the CTX code.
createContext(req, res) {
// create context, request, response objects const context = Object.create(this.context);
const request = context.request = Object.create(this.request);
const response = context.response = Object.create(this.response);
// Context, request, and response are mounted to each other 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; // ③ Record the original URL and return itself context.originalUrl = request.originalUrl = req.url; context.state = {}; return context; } Copy the code
- Create context.js, request.js, response.js, and so on
- Context, request, and response are called by app.ctx. Request, which is called by app.ctx. Request. == ctx.req
- Record the original URL and create a state object and return the context. This is called the context object CTX, and we also know that this context object gets the Request instance, response instance, and the REq and RES returned by Node.
handleRequest
Look again at the handleRequest function that callback eventually returns, on line 162.
handleRequest(ctx, fnMiddleware) {
// ① Set the statusCode of res to 404 const res = ctx.res;
res.statusCode = 404;
// the onError function is called when a catch is defined, and the handleResponse function is returned normally const onerror = err => ctx.onerror(err); const handleResponse = () => respond(ctx); // as the name implies, determine whether to terminate and call a method onFinished(res, onerror); // ④ Call the function previously produced by compose return fnMiddleware(ctx).then(handleResponse).catch(onerror); } Copy the code
- ① Set the res statusCode to 404
- The onError function that is called when a catch is defined, and the handleResponse function that returns normally
- ③ As the name implies, determine whether to end and call a method
- This is the result of the listen > callback > handleRequest call. FnMiddleware is the result of the compose call
middleware1(middleware2(middleware3()))
The function,handleResponseisthis.responseFunction that returns the result by passing CTX, request, response, req, res all the way down and then all the way up.
The response function, which I won’t go into detail about, is relatively simple and basically returns different results according to different return states, mainly including method === “HEAD”, and some processing of res object when returning body type
comb
Finally, I use a diagram to sort out the whole process of application.js
conclusion
In the next chapter, we will talk about context.js. This article will describe the process of learning the source code according to the author. If the writing is not good, it may be a bit of a rundown account, but the author will try to describe it clearly and share some methods and skills for reading the source code. Please click like support.
Related articles
Hand in hand with you to learn Koa source code (a) – directory structure
Learn Koa source code with you hand in hand (2) – Appilication
This article is formatted using MDNICE