和 koa 不同的 express 是怎麼實現
kao和express都是同一個團隊開發的,ofollow,noindex">koa框架會用也會寫—(koa的實現) 已經介紹koa的原理,而koa在express的基礎上進行了優化:
- koa使用了類的概念,express沒有使用類,而是直接使用函式物件,在上面掛載很多方法
- koa封裝了ctx屬性,並且在上面掛載了path、url等屬性,而express沒有ctx,所以其屬性直接掛載在res和req上
- koa將router邏輯從express中抽離出來形成koa-router外掛,所以express中router和中介軟體共用一個佇列,中介軟體預設的路由為'/'
- koa將static從express中抽離出形成koa-static,express自帶static
- koa將views從express中抽離出形成koa-views,express自帶static
- koa將bodyparse從express中抽離出形成koa-bodyparse,express通過body-parser
express的整體框架
從上面的可以知道express的大致框架:
- express是一個物件,上面掛載了static、view、bodyparse等邏輯方法;express也是一個函式,執行會返回一個app物件
- app是一個監聽請求時的處理函式,也是一個物件上面掛載了很多方法。
consthttp = require('http'); const url = require('url'); cosnt methods = require('methods'); //[get,post.......] function createApplication() { //監聽函式 function app(req, res) { let method = req.method.toLowerCase(); let { pathname, query } = url.parse(req.url,true); let index = 0; // 建立了一個next函式,用來派發中介軟體、路由,事項洋蔥模型 function next() { if(app.routes.length == index) return res.end(`Cannot found`) let layer = app.routes[index++]; //這裡中介軟體的處理邏輯和路由的處理邏輯不一樣,下面在介紹 layer.handler(req,res,next); } next(); } //內部封裝了http模組 app.listen = function () { let server = http.createServer(app); server.listen(...arguments) } app.routes = [];//中介軟體和路由佇列 // 批量建立方法:app.get、app.set.....註冊路由 methods.forEach(method => { app[method] = function (pathname, handler) { let layer = { pathname,//路由 handler, //處理函式 method//路由為get、post....,中介軟體為middleware } //處理帶引數的路由/user/:id/:name let keys = []; if(pathname.includes(":")){ let regStr = pathname.replace(/:([^/]*)/g,function () { keys.push(arguments[1]); return '([^\/]*)' }); layer.params = keys;// 路徑引數key陣列[id,name] // 轉化成正則類似/\/user\/([^\/]*)\/([^\/]*)/來匹配請求路徑 // 後面會介紹使用的地方 layer.reg = new RegExp(regStr); } app.routes.push(layer); } }); app.all = function (pathname, handler) { let layer = { pathname, handler, method:'all' } app.routes.push(layer); } // 註冊中介軟體 app.use = function (pathname,handler) { if(typeof handler === 'undefined'){ handler = pathname; pathname = '/';//中介軟體也是一種路由,所有的路由都能匹配 } let layer = { pathname, handler, method:'middleware'//用來和router的方法區別 } app.routes.push(layer); } return app; } module.exports = createApplication; 複製程式碼
擴充套件res和req
function createApplication() { function app(req, res) { let method = req.method.toLowerCase(); let { pathname, query } = url.parse(req.url,true); let index = 0; function next() { if(app.routes.length == index) return res.end(`Cannot found`) let layer = app.routes[index++]; layer.handler(req,res,next); } next(); } app.listen = function () { let server = http.createServer(app); server.listen(...arguments) } app.routes = []; app.use = function (pathname,handler) { if(typeof handler === 'undefined'){ handler = pathname; pathname = '/'; } let layer = { pathname, handler, method:'middleware' } app.routes.push(layer); } // 用app.use註冊了內建中介軟體來擴充套件req和res的 app.use(function (req,res,next) { let method = req.method.toLowerCase(); let { pathname, query } = url.parse(req.url, true); req.path = pathname; req.query = query; req.hostname = req.headers.host.split(':')[0]; //res.send對返回各種型別相容處理 res.send = function (params) { res.setHeader('Content-Type', 'text/html;charset=utf8'); if (typeof params === 'object') {//返回json物件 res.setHeader('Content-Type', 'application/json;charset=utf8'); res.end(util.inspect(params)); } else if (typeof (params) === 'number') {//數字對應狀態碼 res.statusCode = params; res.end(require('_http_server').STATUS_CODES[params]); } else { res.end(params); } } //res.sendFile返回檔案 res.sendFile = function (pathname) { res.setHeader('Content-Type', require('mime').getType(pathname) + ';charset=utf8'); fs.createReadStream(pathname).pipe(res); } //res.redirect重定向 res.redirect = function (pathname) { res.statusCode = 302; res.setHeader('Location',pathname); res.end(); } next(); }) return app; } module.exports = createApplication; 複製程式碼
區分處理中介軟體和路由
function app(req, res) { let method = req.method.toLowerCase(); let { pathname, query } = url.parse(req.url,true); let index = 0; function next(err) { let layer = app.routes[index++]; if(layer){ //中介軟體的處理,包含存在請求路徑處理exp:/user/info匹配/user/ if (layer.method === 'middle') { if (layer.pathname === '/' || req.path.startsWith(layer.pathname + '/') || req.path === layer.pathname) { return layer.handler(req, res, next); //把控制權next給了使用者 } else { next(); // 匹配不到路徑就執行next()匹配下一個中介軟體 } } else { //router處理含請求引數的路由 if (layer.params) { if (layer.method === method && (layer.reg.test(req.path))) { // layer.reg => /\/user\/([^\/]*)\/([^\/]*)/ // req.path => '/user/1/kbz' // matchers => ['/user/1/kbz','1','2'] // layer.params => [id,name] // req.params => {id:'1',name:'kbz'} let matchers = req.path.match(layer.reg); req.params = layer.params.reduce((memo, current, index) => (memo[current] = matchers[index + 1], memo), {}); return layer.handler(req, res); } } //router處理不含請求引數的路由 if ((layer.pathname === req.path || layer.pathname === '*') && (layer.method === method || layer.method === 'all')) { return layer.handler(req, res); } next()//router的處理會自動呼叫next() } }else{ res.end(`Cannot ${pathname} ${method}`); } } next(); } 複製程式碼
next(err)錯誤處理
function app(req, res) { let method = req.method.toLowerCase(); let { pathname, query } = url.parse(req.url,true); let index = 0; function next(err) { let layer = app.routes[index++]; if(layer){ if(err){ //處理錯誤,應該找到錯誤處理中介軟體,特點是擁有4個引數 //由使用者定義,放在對列最後 if (layer.method === 'middle' && layer.handler.length===4 ){ return layer.handler(err,req,res,next) }else{ next(err);//不是錯誤處理中介軟體,就向後繼續查詢 } }else{ if (layer.method === 'middle') { if (layer.pathname === '/' || req.path.startsWith(layer.pathname + '/') || req.path === layer.pathname) { return layer.handler(req, res, next); } else { next(); } } else { if (layer.params) { if (layer.method === method && (layer.reg.test(req.path))) { let matchers = req.path.match(layer.reg); req.params = layer.params.reduce((memo, current, index) => (memo[current] = matchers[index + 1], memo), {}); return layer.handler(req, res); } } if ((layer.pathname === req.path || layer.pathname === '*') && (layer.method === method || layer.method === 'all')) { return layer.handler(req, res); } next() } } }else{ res.end(`Cannot ${pathname} ${method}`); } } next(); } 複製程式碼
內建view渲染邏輯
使用express渲染邏輯主要是呼叫res.render方法,其中使用最多的就是ejs模板引擎,ejs渲染邏輯可以參考koa框架會用也會寫—(koa-view、koa-static)
app.set('views','view');//渲染檔案目錄 app.set('view engine','html');//更改省略的字尾為html,而不是.ejs app.engine('html',require(ejs').__express); //用ejs模板渲染 複製程式碼
function createApplication() { app.use = function (pathname,handler) { if(typeof handler !== 'function'){ handler = pathname; pathname = '/'; } let layer = { method:'middle', pathname, handler } app.routes.push(layer); } // 配置 app.settings = {} app.set = function (key,value) { app.settings[key] = value; } app.engines = {} app.engine = function (ext,renderFn) { app.engines[ext] = renderFn } app.use(function (req,res,next let method = req.method.toLowerCase(); let { pathname, query } = url.parse(req.url, true); req.path = pathname; req.query = query; req.hostname = req.headers.host.split(':')[0]; res.send = function (params) { res.setHeader('Content-Type', 'text/html;charset=utf8'); if (typeof params === 'object') { res.setHeader('Content-Type', 'application/json;charset=utf8'); res.end(util.inspect(params)); } else if (typeof (params) === 'number') { res.statusCode = params; res.end(require('_http_server').STATUS_CODES[params]); } else { res.end(params); } } res.sendFile = function (pathname) { res.setHeader('Content-Type', require('mime').getType(pathname) + ';charset=utf8'); fs.createReadStream(pathname).pipe(res); } res.redirect = function (pathname) { res.statusCode = 302; res.setHeader('Location',pathname); res.end(); } //通過render對頁面進行渲染 res.render = function (filename,obj) { let dirname = app.settings['views'] ; let extname = app.settings['view engine'] ; let p = path.resolve(dirname,filename+'.'+extname); app.engines[extname](p,obj,function (data) { res.end(data); }); } next(); }) } 複製程式碼
內建static邏輯
這裡簡化了邏輯:
createApplication.static = function (dir) { return function (req,res,next) { let p = req.path === '/' ? '/index.html' : req.path let realPath = path.join(dir, p); let flag = fs.existsSync(realPath); if(flag){ fs.createReadStream(realPath).pipe(res); }else{ next(); } } } 複製程式碼
express的bodyparser
express的bodyparser也是通過外掛引入
// body-parser.js function urlencoded() { return (req,res,next)=>{ if (req.headers['content-type']==='application/x-www-form-urlencoded'){ let arr = []; req.on('data',function (data) { arr.push(data); }) req.on('end', function (data) { req.body = require('querystring').parse(Buffer.concat(arr).toString()); next(); }) }else{ next(); } } } function json() { return (req, res, next) => { if (req.headers['content-type'] === 'application/json') { let arr = []; req.on('data', function (data) { arr.push(data); }) req.on('end', function (data) { req.body = JSON.parse(Buffer.concat(arr).toString()); next(); }) } else { next(); } } } module.exports.urlencoded = urlencoded module.exports.json = json 複製程式碼
結語
前面koa框架會用也會寫—(koa的實現) 已經詳細介紹了koa的原理和中介軟體,這裡主要是表示express和koa不同的地方,主要的外掛邏輯可能都簡化了,再次說明express的不同:
- koa使用了類的概念,express沒有使用類,而是直接使用函式物件,在上面掛載很多方法
- koa封裝了ctx屬性,並且在上面掛載了path、url等屬性,而express沒有ctx,所以一些屬性和方法直接掛載在res和req上
- koa將router邏輯從express中抽離出來形成koa-router外掛,所以express中router和中介軟體共用一個佇列,中介軟體預設的路由為'/'
- koa將static從express中抽離出形成koa-static,express自帶static
- koa將views從express中抽離出形成koa-views,express自帶res.render
- koa將bodyparse從express中抽離出形成koa-bodyparse,express自帶bodyparse