#Preface
This article will introduce the boot of Eggjs that is a Node.js web framework.
It is based on Koa and can satisfy your requirement through a large of plugins and middleware, even a your own framework. It is very important to create a cluster, an agent process and some worker processes when it is running. The cluster makes it stronger. Next, we can understand it by reading the source code.
Eggjs has a few major libs, egg-core、egg、egg-cluster、egg-bin、egg-scripts and so on.
egg-core: it extends Koa and is as a parent object of every agent and worker.
egg: it defines some actions for agent and worker, you can almost use these actions to create an app of a single process.
egg-cluster: it creates a cluster and manages them.
egg-scripts and Egg-bin: their job is run the whole app in a different environment.
Tips: We will discuss Eggjs with basing 2.x.x version.
#Scan Libs Code
* The directory and the code segment are not whole content after this post, these major are only for a better explanation.
egg-core
1 | — lib |
The above is the directory structure of egg-core.The egg.js and folder of the loader are important to point for this lib.
See egg.js
1 | const KoaApplication = require('koa') |
First, we can know why it is called that bases on Koa because EggCore extends KoaApplication.
Second, it defines a few new fields in the construction function, such as lifecycle and loader. The loader field helps app for creating important feature include config、plugin、controller、extend、router、middleware、service and so on, it will load some js file in the special directory when the app is starting.
Another side, both beforeStart and ready often are called when we need to write some plugins.
egg
1 | — / app |
There is an egg.js file that is the same name in the egg-core, but it bases on EggCore Class and extends the lifecycle field, creates new messenger field and cluster field and dumps app config info.
See egg.js
1 | const EggCore = require('egg-core').EggCore |
See agent.js and application.js
1 | // agent.js |
We can see that they override the EGG_LOADER property, which uses to create a new loader field in the construction of EggCore that is their parent class. Finally, they call the load function.
We have the base application code. rNext, look a few of libs for starting web app.
egg-scripts and egg-bin
Both these libs start the web app in a different environment. Egg-scripts is easier and more clear, uses the production environment. Egg-bin has much code for helping debug, dev, test and so on, we usually use it in the dev, debug or test environment. We almost don’t know them at most of time.
1 | // package.json |
The scripts command of this eggjs app conforms to the above description.
egg-cluster
The cluster is an important feature for Eggjs. This lib is a bridge for connecting one single agent process and many workers processes. It like a manager.
1 | - / lib |
I had written an article about egg-cluster in Chinese. I always think this is the core of Eggjs, so I will explain the whole Eggjs framework around it.
#From start to getting the first request
What happens when we input npm run dev
or npm start
in the terminal?
- egg-scripts(prod): it can require framework by
child_process.spawn
and calls startCluster function. - egg-bin(dev): it can require framework by
child_process.fork
and calls startCluster function. [parent process]
: The command of running is in one process called the parent process. The system will create a new process called the master process when the parent process requires a framework.As usual, theframework
is the file path of Eggjs.If you want to use a custom framework, you can add a param in the command, such as--framework { your path }
Pseudo Code
1 | // egg-scripts |
Egg-scripts uses spawn
function to require framework while egg-scripts calls fork
function.The latter is a special case of the former. Egg-bin has more code than egg-scripts, these features help us for developing or debug the app.
We have two processes. In general, this newly created process is called the master process.
Why is the master process called birdge?
1 | // egg index.js |
Actually, we exec egg-cluster
‘s startCluster function when we require the framework. We open index.js
file in the egg-cluster lib.
First, it is real enter point for whole web app.
1 | // index.js |
On the other hand, this web will be constructed during creating a Master
object.
- workerManager: it will hold on all worker porcesses.
- messenger: we make
master
a transit station that helps process for communicating (IPC). If you read more code, you can get it. I have said above that it is a bridge. I have write an article(Chinese) about it.
- The Master maintains a Messenger instance (egg-cluster/lib/utils/messenger.js)
- EggApplication maintain the other Messenge instance (egg/lib/core/messenger.js)
- Both the agent and worker process base on EggApplication, them can send info to the master process creating them when calling Messenger. The master process is according to entering params to transmit to the agent or worker, you read forkAppWorkers and forkAgentWorker function in the master
ready.mixin & this.ready: ‘get-ready’ often is used by the official when an object need trigger a few callbacks after it whole initializes. Here it will broadcast an event to the parent, workers and then agent process, telling them that I am ok.
detectPort: apply for an available port, default is 7001.If Successly, it will
fork
an agent process and register a callback message for new created an agent object.[agent process]
: create new Agent and run loadPlugin, loadConfig, loadAgentExtend, loadContextExtend, and loadCustomAgent.loadPlugin: find all plugin, record their dir paths => this.dirs
loadConfig: merge all config, the config content of the app level is more priority than the framework and the latter is more priority than the plugin.
loadAgentExtend: load and merge all of the extending of agent object (app > plugin > core)
loadContextExtend: load and merge all of the extending of context object (app > plugin > core)
loadCustomAgent: it is important that the lifecycle of app boot will be serially triggered. This lifecycle field is defined in EggCore constructor, we can look at the whole process in the yellow background under the image.【Application Startup Configuration(official)】. It help you for better writing a few plugins.
Last, all major boots had been loaded, the agent process will trigger function registered in the callback array, such as sending
'agent-start'
to the master process.
[master process]
: start to fork a few worker processes in accordance with specifying or using the default being CPU kernel count after the master process get'agent-start'
message from the agent process. It will open a new relatable load, we take a single worker process example.[a single worker process]
: create new Agent and run loadPlugin, loadConfig, loadApplicationExtend, loadRequestExtend, loadResponseExtend, loadContextExtend, loadHelperExtend, loadCustomApp, loadService, loadMiddleware, loadController, loadRouter, and loadCustomLoader.loadPlugin: same as the agent
loadConfig: same as the agent
loadApplicationExtend: same as the agent (app > plugin > core)
loadRequestExtend: load and merge all of the extending of request object (app > plugin > core)
loadResponseExtend: load and merge all of the extending of response object (app > plugin > core)
loadContextExtend: same as the agent
loadHelperExtend: load and merge all of the extending of helper object (app > plugin > core)
loadCustomApp: same as the agent (app > plugin)
loadService: load and merge all of the extending of helper object (app > plugin)
loadMiddleware: load middlewares and iterate them => mw, if it conforms the middleware standard, it will be used with app.use(mw) (app > plugin > core)
loadController: iterate all controllers’ functions => key, wrap a function, make it be a middleware function and is bound to controller.xxx.xxx (only app). This middleware will be triggered when getting a new request, the Controller is defined in the
app/controller
directory and the key is it’s function.1
2
3
4
5
6
7
8
9function methodToMiddleware(Controller, key) {
return function classControllerMiddleware(...args) {
const controller = new Controller(this);
if (!this.app.config.controller || !this.app.config.controller.supportParams) {
args = [ this ];
}
return utils.callFn(controller[key], args, controller);
};
}loadRouter: it makes request’s path associated with the controllers’ function that has been become to middleware (only app)
loadCustomLoader: load ourselves function to create some built-in objects for app object or other.Customloader
[master process]
: it listens to all worker processes and triggers the master’s ready once they have finished booting.send
egg-ready
to the parent process, the agent process, the app worker processesLast, traverse BOOTS and run serverDidReady function of each item.
It is the whole boot for Eggjs framework but doesn’t include, such as restarting a worker process when it exits or disconnects, shuting down…
Second, every process is alone if we have not the master. It like a bridge organizing all island, from creation to IPC.
What happens when web app get an Http request?
We only discuss Eggjs code in the applaction layer. :)
The agent
can’t deal with any request because it doesn’t listen port. All request always hand over to workers
. We look at creating worker code, it will run a app.callback function and listen port when it has booted.
This callback is the members of Appliaction in Koa. If server get new request, it will create a new context and handle it. In Eggjs, this handleRequest function has been overwrited. Last, the framework will iterate over all middleware under the current route including the converted controller’s function.
1 | // koa/lib/application |
In summary, we have know how to boot web app and deal with request in Eggjs. When we know it, we can easily write some plugins and middlewares to finish the business requirements.
Comments