如今node的web框架有不少,除express 還有koa egg等等。 但它們本質上仍是基於原生node框架的http。其實框架都大差不差,主要是觀摩和學習。本篇文章主要記錄下本身在node爬坑之路上的經歷和收穫~node
本文主要實現express的功能之一, 匹配路由git
首先咱們看一下express:github
const express = require('express'); let app = new express(); app.get('/',(req,res)=>{ res.end('home Page.'); }); app.get('/center',(req,res)=>{ res.end('center Page.'); }); /** 匹配到動態路由 獲取路由參數並返回 */ app.get('/product/:id/:name',(req,res)=>{ res.end(JSON.stringify(req.params)); }); /** 當以上路徑都沒有匹配成功時 返回404 */ app.all('*',(req,res)=>{ res.end('404'); }); let port = 3000; app.listen(port,()=>{ console.log(`Server is start on port ${port}`); });
ok.代碼很簡單。引入express,new了個express實例,寫了幾個路由,最後啓了本地服務。web
代碼第二行 咱們把引入的express 給new出來,說明express內部返回的是一個function。正則表達式
好.麻雀雖小 五臟俱全,咱們今天就來實現express的這些功能。express
let http = require('http'); /** express基於http */ let url = require('url'); /** 用來解析請求的路徑 */ /** express引入了methods 它的做用是返回各類的請求方法 */ let methods = require('methods'); function application(){ /** 1 express返回了一個函數 * 這個函數就是http.createServer的監聽函數 */ let app = (req,res) => { /** 1.1 url模塊解析 拿到請求路徑 好比 /user */ let { pathname } = url.parse(req.url); /** 1.2 拿到請求方法 方法是大寫 記得轉換爲小寫 */ let requestMethod = req.method.toLowerCase(); /** 1.3 經過拿到的路徑和方法 在以前定義好的路由數組routes中 循環去匹配 */ for(let i = 0; i < app.routes.length; i++){ /** 1.4 解構 拿到每個路由的 路徑 方法 回調 */ let { path, method, cb } = app.routes[i]; if((pathname===path||path==='*') && (requestMethod===method)||method==='all'){ /** 1.5 若是匹配到 返回回調並執行 */ return cb(req,res); } } /** 1.6 沒有匹配到任何路由 */ res.end(`Cannot found ${pathname}/${requestMethod}`); } /** 2 定義一個存放全部路由的數組 */ app.routes = []; /** 2.1 往methods數組中添加一個方法 all 並循環數組 */ [...methods,'all'].forEach((method)=>{ app[method] = function(path,cb){ /** 2.2 先將每一個請求的路由地址 方法和回調保存起來 * path:路徑 method:方法 cb:回調 */ let layer = { path, method, cb }; app.routes.push(layer); } }); /** 3 監聽端口 */ app.listen = function(...arguments){ /** 3.1 利用http的createServer方法 將app傳進去 */ let server = http.createServer(app); server.listen(...arguments); } return app; } /** 4 將方法導出出去 */ module.exports = application;
代碼上面都仔細的標註了觀看序號,1.2.3... 按照順序觀看便可。數組
咱們手寫的整個express就是一個函數 函數裏面return了一個函數。經過node原生框架http的方法 包裝了該函數,最後再將整個函數module.exports導出出去。
最後咱們啓動項目,經過瀏覽器或者postman調用接口,發現確實能實現部分的express功能,可是有一點,此時咱們能實現的僅僅是靜態的路由,若是有路由參數的狀況下,好比/product/:id/:name。結果就不符合預期。 改造:瀏覽器
代碼上面都仔細的標註了觀看序號,1.2.3... 按照順序觀看便可。app
let http = require('http'); let url = require('url'); let methods = require('methods'); function application(){ let app = (req,res) => { let { pathname } = url.parse(req.url); let requestMethod = req.method.toLowerCase(); for(let i = 0; i < app.routes.length; i++){ let { path, method, cb } = app.routes[i]; /** 7 若是請求路徑path中 就說明該路由是動態的 */ if(path.params){ /** 8 匹配該動態路由後面的動態參數 匹配成功返回true */ if(path.test(pathname)){ /** 9 解構賦值 拿到動態路由的參數 */ let [, ...otherParams] = pathname.match(path); /** 10 經過reduce()方法 將路由參數轉換爲對象形式 * 並放到req.params中 */ req.params = path.params.reduce( (memo,key,index)=>( memo[key]=otherParams[index],memo ),{} ); /** 11 返回匹配到的動態路由 */ return cb(req,res); } } if((pathname===path||path==='*') && (requestMethod===method)||method==='all'){ return cb(req,res); } } res.end(`Cannot found ${pathname}/${requestMethod}`); } app.routes = []; [...methods,'all'].forEach((method)=>{ app[method] = function(path,cb){ let layer = { path, method, cb }; /** 1 定義一個空數組 來存放動態路由的參數 */ let params = []; /** 2 若是路徑中包含: 說明該路由是動態路由 */ if(path.includes(':')){ /** 3 更改該動態路由的路徑path爲一個正則表達式 * 目的是爲了等真正請求到來時 匹配到該動態路由 並拿到路由參數 */ layer.path = new RegExp(path.replace(/:([^\/]*)/g,function(){ /** 4 將動態路由參數的key 放入params數組中 */ params.push(arguments[1]); /** 5 返回了一個正則來匹配真正的動態路由參數 注意此處沒有: */ return '([^\/]*)'; })); /** 6 把解析到的動態路由放到該路由路徑path的params上 */ layer.path.params = params; } app.routes.push(layer); } }); app.listen = function(...arguments){ let server = http.createServer(app); server.listen(...arguments); } return app; } module.exports = application;
先經過正則匹配到該動態路由,並把該動態路由的path替換爲一個正則,放到數組中,等待真正的動態路由到來時,從路由數組中拿到該動態路由的路徑,也就是剛纔替換的正則,來匹配該動態路由後的參數便可。框架
經過以上就能實現獲取動態路由的參數 上圖:
代碼在git mock-express