This article was originally published at: github.com/bigo-fronte… Welcome to follow and reprint.
First, install Express
npm install express
Copy the code
Express was installed for demonstration purposes.
Create example.js file
// example.js
const express = require('express')
const app = express()
const port = 3000
app.get('/'.(req, res) = > {
res.send('Hello World! ')
})
app.listen(port, () = > {
console.log(`Example app listening at http://localhost:${port}`)})Copy the code
As the code shows, executing Node example.js starts a server.
As shown in the figure below, we now decide to create an Express file of our own, importing express instead of importing our handwritten Express. .
Ok, now let’s implement our Express!
Have put the code on github: github.com/Sunny-lucki… .
Create the myExpress.js file
const express = require('express')
const app = express()
Copy the code
Express gets a method, and then the method gets app. App is actually a function, and we’ll explain why.
We can preliminarily implement Express as follows:
// myExpress.js
function createApplication() {
let app = function (req,res) {}return app;
}
module.exports = createApplication;
Copy the code
In the code above, you can see that your app has a listen method.
Therefore, we can further add the Listen method to the app:
// myExpress.js
function createApplication() {
let app = function (req,res) {
}
app.listen = function () {}return app;
}
module.exports = createApplication;
Copy the code
What app.listen does is create a server and run it by binding it to a port.
So you can refine the Listen method this way.
// myExpress.js let http = require('http'); function createApplication() { let app = function (req,res) { res.end('hahha'); } app.listen = function () { let server = http.createServer(app) server.listen(... arguments); } return app; } module.exports = createApplication;Copy the code
You might wonder why HTTP. CreateServer (app) is passed to the app.
In fact, we don’t pass in app, which means that app is not a method, but it’s ok.
We could do it like this.
// myExpress.js let http = require('http'); function createApplication() { let app = {}; app.listen = function () { let server = http.createServer(function (req, res) { res.end('hahha') }) server.listen(... arguments); } return app; } module.exports = createApplication;Copy the code
As shown in the code, we can change the app to an object, and there is no problem.
.
Implement the app.get() method
The app.get method takes two arguments, a path and a callback function.
// myExpress.js
let http = require('http');
function createApplication() {
let app = {};
app.routes = []
app.get = function (path, handler) {
let layer = {
method: 'get',
path,
handler
}
app.routes.push(layer)
}
app.listen = function () {
let server = http.createServer(function (req, res) {
res.end('hahha') }) server.listen(... arguments); }return app;
}
module.exports = createApplication;
Copy the code
As shown in the code above, we add a route object to the app, and then when the get method executes, we wrap the two parameters, path and method, into an object and push them into routes.
As you can imagine, when we enter a path in the browser, we will execute the callback function in http.createServer.
So, we need to get the browser’s request path here. Parse to get the path.
Then loop over the routes to find the corresponding route and execute the callback method. As shown in the code below.
// myExpress.js
let http = require('http');
const url = require('url');
function createApplication() {
let app = {};
app.routes = []
app.get = function (path, handler) {
let layer = {
method: 'get',
path,
handler
}
app.routes.push(layer)
}
app.listen = function () {
let server = http.createServer(function (req, res) {
/ / remove the layer
// 1. Get the request method
let m = req.method.toLocaleLowerCase();
let { pathname } = url.parse(req.url, true);
// 2. Find the corresponding route and execute the callback method
for (let i = 0 ; i< app.routes.length; i++){
let {method,path,handler} = app.routes[i]
if (method === m && path === pathname ) {
handler(req,res);
}
}
res.end('hahha') }) server.listen(... arguments); }return app;
}
module.exports = createApplication;
Copy the code
Let’s run the code.Run successfully:
Implement post and other methods.
Simply copy the app.get method and change the value of method to POST.
// myExpress.js
let http = require('http');
const url = require('url');
function createApplication() {... app.get =function (path, handler) {
let layer = {
method: 'get',
path,
handler
}
app.routes.push(layer)
}
app.post = function (path, handler) {
let layer = {
method: 'post', path, handler} app.routes. Push (layer)}...return app;
}
module.exports = createApplication;
Copy the code
This is possible, but there are other methods besides post and GET, so should we write every one of them like this? Of course not. There’s an easy way.
// myExpress.js
function createApplication() {... http.METHODS.forEach(method= > {
method = method.toLocaleLowerCase()
app[method] = function (path, handler) {
letlayer = { method, path, handler } app.routes.push(layer) } }); . }module.exports = createApplication;
Copy the code
As the code shows, HTTP.methods is an array of METHODS. An array as shown below
[” GET “, “POST”, “DELETE”, “PUT”].
By iterating through the array of methods, you can implement all of them.
The test ran, and it was a success.
Implement the app.all method
All means match all methods,
App. all(‘/user’) matches all routes whose path is /user
App.all (‘*’) indicates a route that matches any path and any method
Implementing the All method is also very simple, as shown in the code below
app.all = function (path, handler){
let layer = {
method: "all",
path,
handler
}
app.routes.push(layer)
}
Copy the code
Then you just need to change the router matching logic, as shown in the code below, and just change the judgment.
app.listen = function () {
let server = http.createServer(function (req, res) {
/ / remove the layer
// 1. Get the request method
let m = req.method.toLocaleLowerCase();
let { pathname } = url.parse(req.url, true);
// 2. Find the corresponding route and execute the callback method
for (let i = 0 ; i< app.routes.length; i++){
let {method,path,handler} = app.routes[i]
if ((method === m || method === 'all') && (path === pathname || path === "*")) { handler(req,res); }}console.log(app.routes);
res.end('hahha') }) server.listen(... arguments); }Copy the code
Visible success.
Implementation of middleware app. Use
The implementation of this method is similar to the other methods, as shown in the code.
app.use = function (path, handler) {
let layer = {
method: "middle",
path,
handler
}
app.routes.push(layer)
}
Copy the code
But the problem is, when we use middleware, we use the next method to keep the program going, and how does it execute.
app.use(function (req, res, next) {
console.log('Time:'.Date.now());
next();
});
Copy the code
So we have to implement the next method.
In fact, you can guess that next is a method that calls itself like crazy. That’s recursion
And every time you recurse, the handler that was pushed into the routes is taken out and executed.
In fact, whether app.use or app.all or app.get. In fact, the layer is placed in routes, and then the routes are uniformly traversed to determine whether the handler method in the layer should be executed. Take a look at the implementation of the next method.
function next() {
// We iterated through the entire array, but still did not find a matching path
if (index === app.routes.length) return res.end('Cannot find ')
let { method, path, handler } = app.routes[index++] // Each call to next goes to the next layer
if (method === 'middle') { // Process middleware
if (path === '/' || path === pathname || pathname.starWidth(path + '/')) {
handler(req, res, next)
} else { // Continue traversingnext(); }}else { // Process routing
if ((method === m || method === 'all') && (path === pathname || path === "*")) {
handler(req, res);
} else{ next(); }}}Copy the code
You can see that this is a traversal routes array of recursive methods.
And we can see that with middleware, the middleware will execute as long as path is a “/” or prefix match. Because handler uses the arguments req and res. So this next method is going to be defined in Listen.
The following code looks like this:
// myExpress.js
let http = require('http');
const url = require('url');
function createApplication() {
let app = {};
app.routes = [];
let index = 0;
app.use = function (path, handler) {
let layer = {
method: "middle",
path,
handler
}
app.routes.push(layer)
}
app.all = function (path, handler) {
let layer = {
method: "all",
path,
handler
}
app.routes.push(layer)
}
http.METHODS.forEach(method= > {
method = method.toLocaleLowerCase()
app[method] = function (path, handler) {
let layer = {
method,
path,
handler
}
app.routes.push(layer)
}
});
app.listen = function () {
let server = http.createServer(function (req, res) {
/ / remove the layer
// 1. Get the request method
let m = req.method.toLocaleLowerCase();
let { pathname } = url.parse(req.url, true);
// 2. Find the corresponding route and execute the callback method
function next() {
// We iterated through the entire array, but still did not find a matching path
if (index === app.routes.length) return res.end('Cannot find ')
let { method, path, handler } = app.routes[index++] // Each call to next goes to the next layer
if (method === 'middle') { // Process middleware
if (path === '/' || path === pathname || pathname.starWidth(path + '/')) {
handler(req, res, next)
} else { // Continue traversingnext(); }}else { // Process routing
if ((method === m || method === 'all') && (path === pathname || path === "*")) {
handler(req, res);
} else {
next();
}
}
}
next()
res.end('hahha') }) server.listen(... arguments); }return app;
}
module.exports = createApplication;
Copy the code
When we request the path, we find that the middleware actually executed successfully.
However, the implementation of the median price here is far from perfect.
Because when we use middleware, we don’t have to pass routes. Such as:
app.use((req,res) = > {
console.log("I'm the middle price without routes.");
})
Copy the code
It is very simple to determine whether there is a path to pass, if not, give a default path “/”, the implementation code is as follows:
app.use = function (path, handler) { if(typeof path ! // Handler = path; // Handler = path; path = "/" } let layer = { method: "middle", path, handler } app.routes.push(layer) }Copy the code
Look, isn’t that clever? It’s easy.
We try to access path “/middle”
Yi? The first middleware didn’t execute, why?
By the way, with middleware, next() is executed at the end before it is passed to the next middleware or route.
When we request the “/middle” path, we can see that the request actually succeeds and the middleware executes successfully. That means our logic is fine.
Actually, the middleware is done, but don’t forget, there’s an error middleware?
What is error middleware?
Error-handling middleware functions are defined in much the same way as other middleware functions, except that error-handling functions have four arguments instead of three and have special characteristics (err, REq, RES, next) :
app.use(function(err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke! ');
});
Copy the code
When executing the next() method, if we throw an error, we will directly look for the error middleware to execute, not other middleware or routing.
Here’s an example:
As shown in the figure, when the first middleware passes parameters to next, an execution error occurs. It then skips the rest of the middleware and routing and executes the error middleware directly. Of course, after the execution of the faulty middleware, the execution of the later middleware continues.
Such as:
As shown, the latter of the faulty middleware is executed.
How does that work?
It’s easy to read the code directly, just add another layer of judgment in Next:
function next(err) {
// We iterated through the entire array, but still did not find a matching path
if (index === app.routes.length) return res.end('Cannot find ')
let { method, path, handler } = app.routes[index++] // Each call to next goes to the next layer
if( err ){ // If there are errors, look for middleware execution.
if(handler.length === 4) { // Find the wrong middleware
handler(err,req,res,next)
}else { // Continue to Xuzhou
next(err)
}
}else {
if (method === 'middle') { // Process middleware
if (path === '/' || path === pathname || pathname.starWidth(path + '/')) {
handler(req, res, next)
} else { // Continue traversingnext(); }}else { // Process routing
if ((method === m || method === 'all') && (path === pathname || path === "*")) {
handler(req, res);
} else{ next(); }}}}Copy the code
Check whether err has a value in next by looking at the code visible to determine whether error-finding middleware is needed to execute.
As shown, the request /middle path is successfully executed.
At this point the Implementation of the Express framework is complete. Can you give me a STAR? Thank you guys.
Learn to summarize
Through the implementation of the Express handwriting principle, we have a deeper understanding of the use of Express and find that:
- Middleware and routes are pushed into a Routes array.
- When middleware is executed, next is passed so that the next middleware or route can be executed
- Next is not passed when the route is executed, which causes the routes traversal to end prematurely
- After the error middleware has been executed, subsequent middleware or routing will still be executed.
Welcome everyone to leave a message to discuss, wish smooth work, happy life!
I’m bigO front. See you next time.