瞭解 express 原理之前,你需要先掌握 express 的基本用法。
關於 express 的介紹請看 express 官網。
基本結構
先回顧一下 express 使用的的過程,首先是把模塊倒入,然後當做方法執行,在返回值中調用 use
處理路由,調用 listen
監聽端口。
const express = require('express')
const app = express()
app.use('/home', (req, res) => {
res.end('home')
})
app.listen(8080, () => {
console.log('port created successfully')
})
根據上面的使用,我們開始構建代碼。我們需要寫一個 express
方法,返回一個 app
對象,有 use
和 listen
方法。
const http = require('http')
const url = require('url')
function express() {
const app = {}
const routes = [];
app.use = function (path, action) {
routes.push([path, action])
}
function handle(req, res) {
let pathname = url.parse(req.url).pathname;
for (let i = 0; i < routes.length; i++) {
var route = routes[i];
if (pathname === route[0]) {
let action = route[1];
action(req, res);
return;
}
}
handle404(req, res);
}
function handle404(req, res) {
res.end('404')
}
app.listen = function (...args) {
const server = http.createServer((req, res) => {
handle(req, res)
})
server.listen(...args)
}
return app
}
module.exports = express
上面代碼中的 use
方法的作用是把請求路徑跟對應的處理函數存放在一個數組中,當請求到來的時候遍歷數組,根據路徑找到對應的方法執行。
動態路由
動態路由是根據參數可以動態匹配路徑。
const express = require('./express')
const app = express()
// /home/1
// /home/2
app.use('/home/:id', (req, res) => {
res.end('home')
})
app.listen(8080, () => {
console.log('port created successfully')
})
根據路由裏面的參數要匹配符合規則的路由我們需要使用正則來處理,下面代碼是根據路徑來生成正則的一個方法。
const pathRegexp = (path, paramNames=[], {end=false} ={}) => {
path = path
.concat(end ? '' : '/?')
.replace(/\/\(/g, '(?:/')
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?(\*)?/g, function (_, slash, format, key, capture, optional, star) {
slash = slash || '';
paramNames.push(key);
return ''
+ (optional ? '' : slash)
+ '(?:'
+ (optional ? slash : '')
+ (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')'
+ (optional || '')
+ (star ? '(/*)?' : '');
})
.replace(/([\/.])/g, '\\$1')
.replace(/\*/g, '(.*)');
return new RegExp('^' + path + '$')
}
module.exports = pathRegexp
根據路徑生成正則也是有第三方模塊 path-to-regexp 模塊,核心原理大家值得參考。包括 Vue 和 React 的路由都使用到了這個模塊。
下面我們需要開始動態映射路由。
const http = require('http')
const url = require('url')
const pathRegexp = require('./pathRegexp')
function express() {
const app = {}
const routes = { 'all': [] };
app.use = function (path, action) {
const keys = []
const regexp = pathRegexp(path, keys,{end:true})
routes.all.push([
{ regexp, keys },
action
]);
};
['get', 'put', 'delete', 'post'].forEach(function (method) {
routes[method] = [];
app[method] = function (path, action) {
const keys = []
const regexp = pathRegexp(path, keys, {end:true})
routes[method].push([
{ regexp, keys },
action
]);
};
});
const match = function (pathname, routes, req, res) {
for (var i = 0; i < routes.length; i++) {
let route = routes[i];
let reg = route[0].regexp;
let keys = route[0].keys;
let matched = reg.exec(pathname);
if (matched) {
let params = {};
for (let i = 0, l = keys.length; i < l; i++) {
let value = matched[i + 1];
if (value) {
params[keys[i]] = value;
}
}
req.params = params;
let action = route[1];
action(req, res);
return true;
}
}
return false;
};
function handle(req, res) {
let {pathname, query} = url.parse(req.url, true);
req.query = query
let method = req.method.toLowerCase();
if (routes.hasOwnProperty(method)) {
if (match(pathname, routes[method], req, res)) {
return;
} else {
if (match(pathname, routes.all, req, res)) {
return;
}
}
} else {
if (match(pathname, routes.all, req, res)) {
return;
}
}
handle404(req, res);
}
function handle404 (req, res) {
res.end('404')
}
app.listen = function (...args) {
const server = http.createServer((req, res) => {
handle(req, res)
})
server.listen(...args)
}
return app
}
module.exports = express
其中 express
會把請求的方法都代理到 app
中作爲屬性的方式來方便用戶使用。
const express = require('./express')
const app = express()
app.use('/home/:id', (req, res) => {
console.log(req.params)
res.end('home')
})
// app.get
// app.post
app.get('/user', (req, res) => {
console.log(req.query)
res.end('user')
})
app.listen(8080, () => {
console.log('port created successfully')
})
express 會在請求對象中加一些屬性,會把路徑參數作爲請求時的 params
屬性,會把查詢字符串作爲請求時的 query
屬性。大多數中間件也是這個原理,如 body-parser 模塊,給它加個 body
屬性即可。
通過GitHub查看代碼請點擊:傳送門