preface
In sections 1 and 2, we talked a lot about the use of Express, but didn’t go into details or how it works. So today we will implement our own Express to deepen our understanding of Express. Do not aim to implement all Express code, but to implement its core logic.
The preparatory work
So let’s create a my-Express folder, NPM init to add some basic information. Let’s take a look at what we need and implement it step by step.
- Routes of different paths can be resolved
- Middleware logic processing
- Template file processing, handwritten template engine
- Handles static file access logic
With these points, we can solve the problem step by step.
The routing process
Leaving Express out of the way, let’s take a look at how Node.js builds a server. After all, we’re doing some upper-level encapsulation on Top of Node.js.
http.createServer()
Creating a server in Node.js is simple, with just a few lines of code:
var http = require('http');
http.createServer(function (request, response) {
response.end('Hello World');
}).listen(3000);
Copy the code
After creating a server, we should deal with the logic of the different routes. Create a new express.js file to write our source logic, and a new app.js file to write our business test logic.
- Create a listener function
app
The listener takes two argumentsreq
和res
, both of these parameterscreateServer
The logical parameters that are provided to us to process the request and the logical parameters that are returned app.js
Call thelisten
Method, our server will run
//express.js
const http = require('http')
const url = require('url')
function express() {
function app(req, res) {
}
app.listen = function () {
letserver = http.createServer(app) server.listen(... arguments) }return app
}
module.exports = express
//app.js
var express = require('./express')
var app = express()
app.listen(3000)
Copy the code
Parse the request method and path
We can obtain the method and path of the request by using the following method
let _method = req.method.toLowerCase()
let {
pathname
} = url.parse(req.url, true)
Copy the code
The middleware
Before routing partitions are available, Express can register a route using the middleware method use.
app.use('/user/get'.function(req,res,next){})Copy the code
Before implementing this approach, consider that at the time of development, all the routes are already written, and then the program runs to match the routes according to the method and path. So app.use should be a registration method that registers both routing and middleware, and routing in Express is a type of middleware in general.
app.routes = [];
app.use = function (path, handler) {
if (typeofhandler ! = ='function') {
// This is a middleware function, all routes should be matched to
handler = path
path = '/'
}
let layer = {
// This is a generic routing middleware
method: 'middleware'./ / method middleware
path,
handler,
}
app.routes.push(layer)
}
Copy the code
Once the route has been saved, there should be a scheduling method. This is the next parameter that we often see in Express development. This scheduling method was also written by the author during the interview bytes. Let’s take a look at its implementation:
- traverse
routes
An array of - In the case of middleware, the system determines whether the path matches. If the path matches, the system executes. If the path does not match, the system continues to traverse
- As you can see from this, middleware calls must be called
next
Method, otherwise the following logic will not continue
let index = 0
function next() {
let index = 0
function next() {
if (index === app.routes.length) {
// No match is found
res.end(`Cannot ${_method} ${pathname}`)}let {
method,
path,
handler
} = app.routes[index++] // Each call to next should fetch the next layer
// If it is middleware, decide whether to use the logic of this middleware
if (method === 'middleware') {
if (path === '/' || path === pathname || pathname.startsWith(path + '/')) {
handler(req, res, next)
} else {
next() // There is no match for the current middleware to continue the iteration}}else {
// Process routing
if ((_method === method || method === 'all') && (path === pathname || path === The '*')) {
handler(req, res)
} else {
next()
}
}
}
next()
}
next()
Copy the code
routing
After implementing the middleware access logic, it is time to use the routing method. Express uses a route as follows
//app.js
var userRouter = require('./routes/userRouter')
app.use('/users',userRouter)
// /routes/userRouter.js
var express = require('express');
var router = express.Router();
/* GET users listing. */
router.get('/'.function (req, res, next) {
res.send('respond with a resource');
});
Copy the code
That is, we will implement a Router method to implement the routing partition
- Collect all user-defined routes
routes
An array of - And then to
router
Object joins allhttp
The request ofmethod
app.use
Method to register routes, put all routes are ultimately advancedapp.routes
Array traversal
//express.js
express.Router = function () {
let router = {
routes: []
}
http.METHODS.forEach(method= > {
method = method.toLowerCase()
router[method] = function (path, handler) {
let layer = {
method,
path,
handler
}
router.routes.push(layer)
}
})
router.all = function (path, handler) {
let layer = {
method: 'all'.//method is all
path,
handler
}
app.routes.push(layer)
}
return router
}
/ /...
app.use = function (path, handler) {
if (path && Object.prototype.toString.call(handler) == '[object Object]') {
let basePath = path,
routes = handler.routes
routes.forEach(item= > {
let {
method,
path,
handler
} = item
let layer = {
method,
path: basePath + path,
handler
}
app.routes.push(layer)
})
} else {
/ /...}}Copy the code
Processing parameters
After writing the route, we have to deal with the parameters passed in by the user. Here is a unified encapsulation, added to the REQ object.
await getRequest(req)
function getRequest(req) {
return new Promise((resolve, reject) = > {
/ / get parameters
req.query = url.parse(req.url, true).query
/ / post parameters
let data = ' '
req.on('data'.function (chunk) {
data += chunk;
});
req.on('end'.function () {
data = decodeURI(data);
vardataObject = querystring.parse(data); req.body = dataObject resolve() }); })}Copy the code
At this point, we have finished writing the middleware and routing.
Template file processing
Next let’s deal with the view layer of the MVC framework, the Views template file. Remember the page from section 1 when you first opened the Express sample code? That’s a view file for rendering. Create a new views folder under the root directory. Then create a new index. TPL file with our own hh suffix. Let’s start with some sample code:
// routes/users.js
router.get('/my'.(req, res, next) = > {
res.render('index', {
title: 'my'.user: {
name: 'my-express'}})})Copy the code
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<title><%= title%></title>
</head>
<body>
<% if (user) { %>
<h2><%= user.name %></h2>The < %} % ></body>
</html>
Copy the code
We want the user to render the index.tpl template and display the parameters of the render method when using a browser to localhost:3000/users/my. It is also agreed that the expression is <% %>. Let’s implement the Render method first
render
The main idea of render method is as follows:
- in
res
Parameter injectionrender
methods - Call the
render
Method, reads the static file and finally spits out a page
function render(filePath,title, options) {
if (filePath[0] = ='/') filePath.splice(0.1)
fs.readFile(`./views/${filePath}.tpl`.(err, data) = > {
if (err) {
throw new Error(err)
} else {
let buffer = compile(data, options)// Compile the template
res.write(data)
res.end()
}
})
}
Copy the code
The resulting page is uncompiled, meaning we also need a way to compile the template and replace the values passed in with the HTML. Here directly take a well-known industry template engine to deal with -EJS. See the EJS documentation for EJS documentation.
Install EJS: NPM install EJS –save
We can then use EJS’s render method to convert the template string we wrote to an HTML string.
function compile(tpl, options) {
tpl = tpl.toString();
let str = ejs.render(tpl, options)
return Buffer.from(str)
}
Copy the code
At this point, our module parsing is complete.
Static file processing
At last. Our framework still lacks a static resource access logic. Let’s deal with that
- To avoid some conflicts, parsimonious coding, we’re not like
Express
So internally, we’re going to route tolocalhost:3000/static/xxx
- After obtaining the file, spit it out, like template reading
- Special handling
static
The logic of the
function getStatic(pathname, res) {
let _path = pathname.slice(1).split('/')
if (_path[0] = ='static') {
let filePath = '/' + _path.join('/')
fs.readFile(filePath, (err, data) = > {
res.write(data)
res.end()
})
}
}
Copy the code
The last
At this point, the Express series is over everywhere. When you implement your own Express, you may still have some problems with code encapsulation, exception handling, and so on. But our initial goal may not be to build a wheel that works in an identical build environment, but rather to write some core logic and learn the core ideas of an MVC framework in a small way.
Thank you for reading, if you like, you can help to click a “like” yo ~