When it comes to Node.js development, Express and Koa are two big frameworks that are hot right now.
Express is a minimum-sized flexible Node.js Web application development framework that provides a powerful set of capabilities for Web and mobile applications. There are many users at present.
Koa is a new Web framework, built by the same people behind Express, that aims to be a smaller, more expressive, and more robust cornerstone of web application and API development. By making use of async functions, Koa helps you discard callback functions and greatly enhances error handling. Koa does not bundle any middleware, but rather provides an elegant way to help you write server-side applications quickly and happily.
Those of you who have some knowledge of both frameworks will have some understanding of their middleware mechanisms, with Express as a linear model and Koa as an onion model. This series of blogs will focus on the middleware mechanisms of Express and Koa, and this one will focus on the middleware mechanisms of Express.
Express middleware
Connect was the core of Express 3.x before Express 3.x, but Express 4.x has removed Connect and implemented the connect interface in Express itself, so we will use connect source code directly in this article.
The sample
Here is a simple demo using Express to illustrate the middleware mechanism
var express = require('express');
var app = express();
app.use(function (req, res, next) {
console.log('First middleware start');
setTimeout((a)= > {
next();
}, 1000)
console.log('First middleware end');
});
app.use(function (req, res, next) {
console.log('Second middleware start');
setTimeout((a)= > {
next();
}, 1000)
console.log('Second middleware end');
});
app.use('/foo'.function (req, res, next) {
console.log('Interface logic start');
next();
console.log('Interface logic end');
});
app.listen(4000);
Copy the code
In this case, the output is consistent with our understanding of Express linearity, and the output is
First middleware start First middleware end Second middleware start Second middleware end interface logic Start Interface logic endCopy the code
However, if we cancel the asynchronous processing inside the middleware and call next() directly
var express = require('express');
var app = express();
app.use(function (req, res, next) {
console.log('First middleware start');
next()
console.log('First middleware end');
});
app.use(function (req, res, next) {
console.log('Second middleware start');
next()
console.log('Second middleware end');
});
app.use('/foo'.function (req, res, next) {
console.log('Interface logic start');
next();
console.log('Interface logic end');
});
app.listen(4000);
Copy the code
The output is
The first middleware start the second middleware START interface logic Start interface logic end The second middleware end the first middleware endCopy the code
Isn’t the result similar to the Koa output? Yes, but it’s not the same as peeling the onion, and the output is due to synchronous code execution, not to say that Express is not a linear model.
When there is no asynchronous operation in our middleware, our code actually ends up running like this
app.use(function middleware1(req, res, next) {
console.log('First middleware start')
// next()
(function (req, res, next) {
console.log('Second middleware start')
// next()
(function (req, res, next) {
console.log('Interface logic start')
// next()
(function handler(req, res, next) {
// do something}) ()console.log('Interface logic end')
})()
console.log('Second middleware end')
})()
console.log('First middleware end')})Copy the code
This is actually the implementation of CONNECT. Next, we will parse its source code
Connect source code
The connect source code is just over 200 lines long, but this article only selects some of the core code, not the whole code. To see the whole code, go to Github
Middleware mount mainly depends on proto.use and proto.handle. Here we delete part of if judgment to make us focus more on the implementation of its internal principle
proto.use = function use(route, fn) {
var handle = fn;
var path = route;
// the callback function is fault-tolerant
// default route to '/'
if (typeofroute ! = ='string') {
handle = route;
path = '/';
}
.
.
.
this.stack.push({ route: path, handle: handle });
return this;
};
Copy the code
Proto. use mainly stores the middleware we need to mount on its own stack property, while doing partial compatibility processing, which is easier to understand. The core of its middleware mechanism is the realization of the next method inside Proto. Handle.
proto.handle = function handle(req, res, out) {
var index = 0;
var stack = this.stack;
function next(err) {
// next callback
var layer = stack[index++];
// all done
if(! layer) { defer(done, err);return;
}
// route data
var path = parseUrl(req).pathname || '/';
var route = layer.route;
// skip this layer if the route doesn't match
if (path.toLowerCase().substr(0, route.length) ! == route.toLowerCase()) {return next(err);
}
// call the layer handle
call(layer.handle, route, err, req, res, next);
}
next();
};
Copy the code
After deleting some non-core codes, it can be clearly seen that the core of Proto. handle is the realization and recursive call of next method, which takes out and executes middleware existing in stack.
This explains the difference in output between asynchronous and non-asynchronous processes described above.
- When there is asynchronous code, the execution will be directly skipped and continue. At this time, the next method is not executed and we need to wait for all the events in the current queue to complete execution, so the output data is linear at this time.
- When the Next method is executed directly, essentially all the code is already synchronized, so the layers are nested, and the outermost one is sure to end up producing something like peeling an onion.
conclusion
The basic principle of the implementation of CONNECT is to maintain a stack array, push all the middleware to be mounted into the array after processing, and then execute the next method in the array until all the middleware is mounted. Of course, some exceptions, compatibility and other processing will be done in this process.
subsequent
Analysis of Express and Koa middleware Mechanism (II) Mainly analyzes the implementation mechanism of Koa2 middleware