滬江CCtalk視頻地址:https://www.cctalk.com/v/15114923887518html
愛能遮掩一切過錯。node
當咱們在訪問一個站點的時候,若是訪問的地址不存在(404),或服務器內部發生了錯誤(500),站點會展現出某個特定的頁面,好比:npm
那麼如何在 Koa
中實現這種功能呢?其實,一個簡單的中間件便可實現,咱們把它稱爲 http-error
。實現過程並不複雜,拆分爲三步來看:小程序
打造一個事物前,須要先確認它具備什麼特性,這就是需求。微信小程序
在這裏,稍微整理下便可獲得幾個基本需求:服務器
400
、 500
類錯誤碼的時候,引導用戶至錯誤頁面;如今,從一個請求進入 Koa
開始提及:微信
Koa
,出現了錯誤;http-error
中間件捕捉到;能夠看到,關鍵點就是捕捉錯誤,以及實現錯誤處理邏輯和渲染頁面邏輯。app
基於教程目錄結構,咱們建立 middleware/mi-http-error/index.js
文件,存放中間件的邏輯代碼。初始目錄結構以下:koa
middleware/ ├─ mi-http-error/ │ └── index.js └─ index.js
注意: 目錄結構不存在,須要本身建立。async
該中間件第一項須要實現的功能是捕捉到全部的 http
錯誤。根據中間件的洋蔥模型,須要作幾件事:
修改 middleware/index.js
,引入 mi-http-error
中間件,並將它放到洋蔥模型的最外層
const path = require('path') const ip = require("ip") const bodyParser = require('koa-bodyparser') const nunjucks = require('koa-nunjucks-2') const staticFiles = require('koa-static') const miSend = require('./mi-send') const miLog = require('./mi-log') // 引入請求錯誤中間件 const miHttpError = require('./mi-http-error') module.exports = (app) => { // 應用請求錯誤中間件 app.use(miHttpError()) app.use(miLog(app.env, { env: app.env, projectName: 'koa2-tutorial', appLogLevel: 'debug', dir: 'logs', serverIp: ip.address() })); app.use(staticFiles(path.resolve(__dirname, "../public"))) app.use(nunjucks({ ext: 'html', path: path.join(__dirname, '../views'), nunjucksConfig: { trimBlocks: true } })); app.use(bodyParser()) app.use(miSend()) }
修改 mi-http-error/index.js
,在中間件內部對內層的其它中間件進行錯誤監聽,並對捕獲 catch
到的錯誤進行處理
module.exports = () => { return async (ctx, next) => { try { await next(); /** * 若是沒有更改過 response 的 status,則 koa 默認的 status 是 404 */ if (ctx.response.status === 404 && !ctx.response.body) ctx.throw(404); } catch (e) { /*此處進行錯誤處理,下面會講解具體實現*/ } } }
上面的準備工做作完,下面實現兩個關鍵邏輯。
錯誤處理邏輯其實很簡單,就是對錯誤碼進行判斷,並指定要渲染的文件名。這段代碼運行在錯誤 catch
中。
修改 mi-http-error/index.js
:
module.exports = () => { let fileName = 'other' return async (ctx, next) => { try { await next(); /** * 若是沒有更改過 response 的 status,則 koa 默認的 status 是 404 */ if (ctx.response.status === 404 && !ctx.response.body) ctx.throw(404); } catch (e) { let status = parseInt(e.status) // 默認錯誤信息爲 error 對象上攜帶的 message const message = e.message // 對 status 進行處理,指定錯誤頁面文件名 if(status >= 400){ switch(status){ case 400: case 404: case 500: fileName = status; break; // 其它錯誤 指定渲染 other 文件 default: fileName = 'other' } } } } }
也就是說,對於不一樣的狀況,會展現不一樣的錯誤頁面:
├─ 400.html ├─ 404.html ├─ 500.html ├─ other.html
這幾個頁面文件咱們會在後面建立,接下來咱們開始講述下頁面渲染的問題。
首先咱們建立默認的錯誤頁面模板文件 mi-http-error/error.html
,這裏採用 nunjucks
語法。
<!DOCTYPE html> <html> <head> <title>Error - {{ status }}</title> </head> <body> <div id="error"> <h1>Error - {{ status }}</h1> <p>Looks like something broke!</p> {% if (env === 'development') %} <h2>Message:</h2> <pre> <code> {{ error }} </code> </pre> <h2>Stack:</h2> <pre> <code> {{ stack }} </code> </pre> {% endif %} </div> </body> </html>
由於牽涉到文件路徑的解析,咱們須要引入 path
模塊。另外,還須要引入 nunjucks
工具來解析模板。path
是 node
模塊,咱們只需從 npm
上安裝nunjucks
便可。
安裝 nunjucks
模塊來解析模板文件:
npm i nunjucks -S
修改 mi-http-error/index.js
,引入 path
和 nunjucks
模塊:
// 引入 path nunjucks 模塊 const Path = require('path') const nunjucks = require('nunjucks') module.exports = () => { // 此處代碼省略,與以前同樣 }
爲了支持自定義錯誤文件目錄,原來調用中間件的代碼須要修改一下。咱們給中間件傳入一個配置對象,該對象中有一個字段 errorPageFolder
,表示自定義錯誤文件目錄。
修改 middleware/index.js
:
// app.use(miHttpError()) app.use(miHttpError({ errorPageFolder: path.resolve(__dirname, '../errorPage') }))
注意: 代碼中,咱們指定了 /errorPage
爲默認的模板文件目錄。
修改 mi-http-error/index.js
,處理接收到的參數:
const Path = require('path') const nunjucks = require('nunjucks') module.exports = (opts = {}) => { // 400.html 404.html other.html 的存放位置 const folder = opts.errorPageFolder // 指定默認模板文件 const templatePath = Path.resolve(__dirname, './error.html') let fileName = 'other' return async (ctx, next) => { try { await next() if (ctx.response.status === 404 && !ctx.response.body) ctx.throw(404); } catch (e) { let status = parseInt(e.status) const message = e.message if(status >= 400){ switch(status){ case 400: case 404: case 500: fileName = status; break; default: fileName = 'other' } }else{// 其它狀況,統一返回爲 500 status = 500 fileName = status } // 肯定最終的 filePath 路徑 const filePath = folder ? Path.join(folder, `${fileName}.html`) : templatePath } } }
路徑和參數準備好以後,咱們須要作的事情就剩返回渲染的頁面了。
修改 mi-http-error/index.js
,對捕捉到的不一樣錯誤返回相應的視圖頁面:
const Path = require('path') const nunjucks = require('nunjucks') module.exports = (opts = {}) => { // 增長環境變量,用來傳入到視圖中,方便調試 const env = opts.env || process.env.NODE_ENV || 'development' const folder = opts.errorPageFolder const templatePath = Path.resolve(__dirname, './error.html') let fileName = 'other' return async (ctx, next) => { try { await next() if (ctx.response.status === 404 && !ctx.response.body) ctx.throw(404); } catch (e) { let status = parseInt(e.status) const message = e.message if(status >= 400){ switch(status){ case 400: case 404: case 500: fileName = status; break; default: fileName = 'other' } }else{ status = 500 fileName = status } const filePath = folder ? Path.join(folder, `${fileName}.html`) : templatePath // 渲染對應錯誤類型的視圖,並傳入參數對象 try{ // 指定視圖目錄 nunjucks.configure( folder ? folder : __dirname ) const data = await nunjucks.render(filePath, { env: env, // 指定當前環境參數 status: e.status || e.message, // 若是錯誤信息中沒有 status,就顯示爲 message error: e.message, // 錯誤信息 stack: e.stack // 錯誤的堆棧信息 }) // 賦值給響應體 ctx.status = status ctx.body = data }catch(e){ // 若是中間件存在錯誤異常,直接拋出信息,由其餘中間件處理 ctx.throw(500, `錯誤頁渲染失敗:${e.message}`) } } } }
上面所作的是使用渲染引擎對模板文件進行渲染,並將生成的內容放到 Http
的 Response
中,展現在用戶面前。感興趣的同窗能夠去中間件源碼中查看 error.html
查看模板內容(實際上是從 koa-error
那裏拿來稍做修改的)。
在代碼的最後,咱們還有一個異常的拋出 ctx.throw()
,也就是說,中間件處理時候也會存在異常,因此咱們須要在最外層作一個錯誤監聽處理。
修改 middleware/index.js
:
const path = require('path') const ip = require("ip") const bodyParser = require('koa-bodyparser') const nunjucks = require('koa-nunjucks-2') const staticFiles = require('koa-static') const miSend = require('./mi-send') const miLog = require('./mi-log') const miHttpError = require('./mi-http-error') module.exports = (app) => { app.use(miHttpError({ errorPageFolder: path.resolve(__dirname, '../errorPage') })) app.use(miLog(app.env, { env: app.env, projectName: 'koa2-tutorial', appLogLevel: 'debug', dir: 'logs', serverIp: ip.address() })); app.use(staticFiles(path.resolve(__dirname, "../public"))) app.use(nunjucks({ ext: 'html', path: path.join(__dirname, '../views'), nunjucksConfig: { trimBlocks: true } })); app.use(bodyParser()) app.use(miSend()) // 增長錯誤的監聽處理 app.on("error", (err, ctx) => { if (ctx && !ctx.headerSent && ctx.status < 500) { ctx.status = 500 } if (ctx && ctx.log && ctx.log.error) { if (!ctx.state.logged) { ctx.log.error(err.stack) } } }) }
下面,咱們增長對應的錯誤渲染頁面:
建立 errorPage/400.html
:
<!DOCTYPE html> <html> <head> <title>400</title> </head> <body> <div id="error"> <h1>Error - {{ status }}</h1> <p>錯誤碼 400 的描述信息</p> {% if (env === 'development') %} <h2>Message:</h2> <pre> <code> {{ error }} </code> </pre> <h2>Stack:</h2> <pre> <code> {{ stack }} </code> </pre> {% endif %} </div> </body> </html>
建立 errorPage/404.html
:
<!DOCTYPE html> <html> <head> <title>404</title> </head> <body> <div id="error"> <h1>Error - {{ status }}</h1> <p>錯誤碼 404 的描述信息</p> {% if (env === 'development') %} <h2>Message:</h2> <pre> <code> {{ error }} </code> </pre> <h2>Stack:</h2> <pre> <code> {{ stack }} </code> </pre> {% endif %} </div> </body> </html>
建立 errorPage/500.html
:
<!DOCTYPE html> <html> <head> <title>500</title> </head> <body> <div id="error"> <h1>Error - {{ status }}</h1> <p>錯誤碼 500 的描述信息</p> {% if (env === 'development') %} <h2>Message:</h2> <pre> <code> {{ error }} </code> </pre> <h2>Stack:</h2> <pre> <code> {{ stack }} </code> </pre> {% endif %} </div> </body> </html>
建立 errorPage/other.html
:
<!DOCTYPE html> <html> <head> <title>未知異常</title> </head> <body> <div id="error"> <h1>Error - {{ status }}</h1> <p>未知異常</p> {% if (env === 'development') %} <h2>Message:</h2> <pre> <code> {{ error }} </code> </pre> <h2>Stack:</h2> <pre> <code> {{ stack }} </code> </pre> {% endif %} </div> </body> </html>
errorPage
中的頁面展現內容,能夠根據本身的項目信息修改,以上僅供參考。
至此,咱們基本完成了用來處理『請求錯誤』的中間件。而這個中間件並非固定的形態,你們在真實項目中,還須要多考慮本身的業務場景和需求,打造出適合本身項目的中間件。
下一節中,咱們將學習下規範與部署——制定合適的團隊規範,提高開發效率。
上一篇:iKcamp新課程推出啦~~~~~iKcamp|基於Koa2搭建Node.js實戰(含視頻)☞ 處理靜態資源