目錄javascript
const Koa = require('koa') const app = new Koa() app.use( async ( ctx ) => { ctx.body = 'hello koa2' }) app.listen(3000)
源碼文件主要包含了 application.js 、context.js 、request.js 、response.jscss
這裏主要介紹如何使用 async/await 在 koa2 中進行中間件的開發html
middleware 在 koa2 中如何使用java
const Koa = require('koa') const logger = require('./middleware/logger-async') const app = new Koa() app.use(logger()) app.use(ctx => { ctx.body = 'hello middleware' }) app.listen(3000)
如何編寫一個簡單的 middleware 中間件node
function log(ctx) { console.log( ctx.method, ctx.header.host + ctx.url ) } module.exports = function() { return async function(ctx, next) { log(ctx) await next() } } // 對,就是這樣,so easy
原生 JS 實現 koa 的 routermysql
通過思考🤔, 實現路由的基本原理: 經過請求進來的 url 匹配到對應的頁面文件,而後經過 fs 讀取對應文件的內容,並返回給 ctx.body, 那下面咱們就按照這個思路來實現一下路由。git
function render(page) { return new Promise((resolve, reject) => { let viewUrl = `./view/${page}`; fs.readFile(viewUrl, 'utf8', (err, data) => { if (err) { reject(err); } else { resolve(data); } }); }); } async function route(url) { let view = '404.html'; switch (url) { case '/': view = 'index.html'; break; case '/index': view = 'index.html'; break; case '/login': view = 'login.html'; break; case '/404': view = '404.html'; break; default: break; } let html = render(view); return html; } app.use(async ctx => { let url = ctx.request.url; let html = await route(url); ctx.body = html; }); // 固然還有 koa-router 中間件
GET 請求數據獲取的方法有2中,以下github
app.use(async ctx => { let url = ctx.request.url; let html = await route(url); // 從上下文對 request 對象中獲取 let request = ctx.request; let req_query = request.query; let req_queryString = request.querystring; // 從 上下文中直接獲取 let ctx_query = ctx.query; let ctx_queryString = ctx.querystring; ctx.body = { ctx, request, url, req_query, req_queryString, ctx_query, ctx_queryString, html }; });
返回結果sql
url: "/index?page=1" req_query: {page: "1"} req_queryString: "page=1" ctx_query: {page: "1"} ctx_queryString: "page=1"
疑惑🤔的 點: 從上線文中獲取的request對象和直接經過上線文獲取的參數 有什麼區別? 爲何要這麼設計?chrome
注意:ctx.request是context通過封裝的請求對象,ctx.req是context提供的node.js原生HTTP請求對象, 和這裏的 ctx.query 和 ctx.request.query 是沒有關係的。
POST 請求的話,須要咱們在頁面mock一個表單,這樣的話,能夠更好的查看咱們請求的數據。
<h1>koa2 request post demo</h1> <form method="POST" action="/"> <p>userName</p> <input name="userName" /><br /> <p>nickName</p> <input name="nickName" /><br /> <p>email</p> <input name="email" /><br /> <button type="submit">submit</button> </form>
if (ctx.method === 'GET') { ctx.body = html; } else if (ctx.url === '/' && ctx.method === 'POST') { ctx.body = html + `<script> alert('提交成功!') </script>`; } else { ctx.body = '<h1>404!!! o(╯□╰)o</h1>'; }
其實是封裝了一層 post 的數據處理方法,而後將其賦值給了 ctx.request 的 body 屬性
const bodyParser = require('koa-bodyparser') // 使用ctx.body解析中間件 app.use(bodyParser()) // 處理 method 爲 POST 的方法 let postData = ctx.request.body ctx.body = postData
// 核心代碼 │ ├── content.js # 讀取請求內容 │ ├── dir.js # 讀取目錄內容 │ ├── file.js # 讀取文件內容 │ ├── mimes.js # 文件類型列表 │ └── walk.js # 遍歷目錄內容 └── index.js # 啓動入口文件
4.1.一、index.js 入口文件(對於文本類型和圖片類型返回請求數據的方式是不同的)
// 核心部分代碼 - 非所有 // 輸出靜態資源內容 if ( _mime && _mime.indexOf('image/') >= 0 ) { // 若是是圖片,則用node原生res,輸出二進制數據 ctx.res.writeHead(200) ctx.res.write(_content, 'binary') ctx.res.end() } else { // 其餘則輸出文本 ctx.body = _content }
4.1.二、content.js 爲讀取當前請求內容 (判斷當前文件請求路徑是是否存在且判斷是 文件夾仍是文件, 若是是文件夾則讀取文件內容)
// 核心代碼 //判斷訪問地址是文件夾仍是文件 let stat = fs.statSync( reqPath ) if( stat.isDirectory() ) { //若是爲目錄,則渲讀取目錄內容 content = dir( ctx.url, reqPath ) } else { // 若是請求爲文件,則讀取文件內容 content = await file( reqPath ) }
4.1.三、dir.js 爲讀取目錄內容
// 核心部分代碼 // 遍歷讀取當前目錄下的文件、子目錄 let contentList = walk( reqPath )
4.1.四、 file.js 讀取文件內容
// 核心代碼,讀取對應文件的內容(此處讀取出來的文件內行) function file ( filePath ) { let content = fs.readFileSync(filePath[, options]) return content } // 這裏須要註釋一下 fs.readFileSync(filePath[, options]) 中的 options 分別有 encoding 和 flag 二種選項,其中若是指定了 encoding 選項,則此函數返回字符串,不然返回 buffer。 就是說默認爲 buffer
4.1.五、 mimes.js 文件類型列表
let mimes = { 'css': 'text/css', 'less': 'text/css', 'gif': 'image/gif', 'html': 'text/html', 'ico': 'image/x-icon', 'jpeg': 'image/jpeg', 'jpg': 'image/jpeg', 'js': 'text/javascript', 'json': 'application/json', 'pdf': 'application/pdf', 'png': 'image/png', 'svg': 'image/svg+xml', 'swf': 'application/x-shockwave-flash', 'tiff': 'image/tiff', 'txt': 'text/plain', 'wav': 'audio/x-wav', 'wma': 'audio/x-ms-wma', 'wmv': 'video/x-ms-wmv', 'xml': 'text/xml' } // 其中除了咱們常見的 text/xxx 的文本類型、還有 image/xxx 圖片類型和等等等
4.1.六、 walk.js 文件類型列表
// 核心代碼 經過遍歷,獲得當前文件夾內的文件夾名稱、和最後的文件名稱 let result = dirList.concat( fileList ); // 疑惑的點: 爲何須要把文件名稱也加上呢? 你們也能夠做爲一個思考
簡單粗暴的直接上代碼吧, 裏面有一些須要注意的問題點,都在註釋點中了。關鍵點就在與 koa 自己提供了 cookie 的 set 和 get 方法,能夠很是簡單的獲取到對應想要的,可是裏面咱們常見的一些設置的參數,簡單看一眼,其實就很是不簡單了,maxAge、expires、httpOnly、overwrite 等等,這些都是咱們在使用 cookie 的時候須要注意的,安全問題,http 請求問題。每一點都值得仔細來說講。
app.use(async ctx => { if (ctx.url === '/index') { ctx.cookies.set('cid', 'hello world', { domain: '127.0.0.1', // 寫cookie所在的域名, 須要注意的是若是訪問的域名和這裏的 domain 不一致的化,是沒法成功寫入的 path: '/index', // 寫cookie所在的路徑 maxAge: 10 * 60 * 1000, // cookie有效時長 expires: new Date('2017-02-15'), // cookie失效時間 httpOnly: false, // 是否只用於http請求中獲取 overwrite: false // 是否容許重寫 }); ctx.body = 'cookies is ok'; } else { ctx.body = 'hello koa2'; } });
這裏須要注意下,koa 自己沒有提供 session 的方法,這裏的例子是經過中間件來實現一些你須要的能力。這裏的兩種實現 session 能力的方案。這兩個方案的區別就在於 存儲信息的大小。
使用 koa-session 中間件的核心在於須要對於給出的 對應 config 配置的理解。
const Koa = require('koa'); // 導入Koa const Koa_Session = require('koa-session'); // 導入koa-session // 配置 const session_signed_key = ["some secret hurr"]; // 這個是配合signed屬性的簽名key const session_config = { key: 'koa:sess', /** cookie的key。 (默認是 koa:sess) */ maxAge: 4000, /** session 過時時間,以毫秒ms爲單位計算 。*/ autoCommit: true, /** 自動提交到響應頭。(默認是 true) */ overwrite: true, /** 是否容許重寫 。(默認是 true) */ httpOnly: true, /** 是否設置HttpOnly,若是在Cookie中設置了"HttpOnly"屬性,那麼經過程序(JS腳本、Applet等)將沒法讀取到Cookie信息,這樣能有效的防止XSS攻擊。 (默認 true) */ signed: true, /** 是否簽名。(默認是 true) */ rolling: true, /** 是否每次響應時刷新Session的有效期。(默認是 false) */ renew: false, /** 是否在Session快過時時刷新Session的有效期。(默認是 false) */ }; // 而後經過 ctx.session.logged 來判斷當前用戶是否登錄成功、是否在有效期內等等
// session 中間件 app.use( session({ key: 'SESSION_ID', store: store, cookie: cookie }) ); // 數據庫配置 let store = new MysqlSession({ user: 'root', password: '123456', database: 'hellothinkjs', host: '127.0.0.1' }); // 存放sessionId的cookie配置 let cookie = { maxAge: '', // cookie有效時長 expires: '', // cookie失效時間 path: '', // 寫cookie所在的路徑 domain: '', // 寫cookie所在的域名 httpOnly: true, // 是否只用於http請求中獲取 overwrite: '', // 是否容許重寫 secure: '', sameSite: '', signed: '' };
這裏直接展現使用的 demo
app.use( views(path.join(__dirname, './ejs'), { extension: 'ejs' }) ); app.use(async ctx => { let title = 'hello 404'; await ctx.render('404', { title }); });
另外,咱們附上 ejs 官方文檔
這裏寫了一個簡單的 demo 大體的介紹了下,koa 中 mysql 的使用方式
// 鏈接數據庫 const connection = mysql.createConnection({ host: '127.0.0.1', // 數據庫地址 user: 'root', // 數據庫用戶 password: '123456', // 數據庫密碼 database: 'hellothinkjs' // 選中數據庫 }); let title = 'hello 404'; let users = []; connection.connect(); connection.query('SELECT * FROM think_user', async (error, results, fields) => { if (error) throw error; // connected ! console.log(results); users = results; app.use(async ctx => { await ctx.render('404', { title, users }); }); }); connection.end();
這裏須要注意一點的是: 由於網上以前找的文檔中,不少關於 mysql modules 的使用方式比較古老了,不太適合新版本的 mysql 的連接和使用。 mysql 最新使用文檔
這裏的單元測試主要是正對 node 提供的API 服務來進行測試,測試框架選擇: mocha(測試框架)、chai(斷言庫,用來判斷是否知足預期結果)、supertest(用來模擬 API 請求)固然這三個庫,每個看上去都會有更多的特性,這裏只是簡單的介紹了一些基礎自動化測試的demo
// api.js api server const server = async (ctx, next) => { let result = { success: true, data: null }; if (ctx.method === 'GET') { if (ctx.url === '/getString.json') { result.data = 'this is string data'; } else if (ctx.url === '/getNumber.json') { result.data = 123456; } else { result.success = false; } ctx.body = result; next && next(); } else if (ctx.method === 'POST') { if (ctx.url === '/postData.json') { result.data = 'ok'; } else { result.success = false; } ctx.body = result; next && next(); } else { ctx.body = 'hello world'; next && next(); } };
// index.test.js test server describe('開始測試智商稅了', () => { // 測試用例 it('測試你的智商是否是二百五', done => { request .post('/postData.json') .expect(200) .end((err, res) => { // 斷言判斷結果是否爲object類型 expect(res.body).to.be.an('object'); expect(res.body.success).to.be.an('boolean'); expect(res.body.data).to.be.an('string'); done(); }); }); }); // 這裏發現,咱們在測試咱們的藉口返回數據的類型、數值、錯誤碼等類型的時候會有很是大的幫助的。之後若是須要用起來的話,推薦使用之。
vscode 自帶 debug 能力,這裏須要花費必定時間去理解的地方是 debug 啓動程序的時候,須要配置一個 launch.json 文件,這裏給一個對應的 demo。
{ // 使用 IntelliSense 瞭解相關屬性。 // 懸停以查看現有屬性的描述。 // 欲瞭解更多信息,請訪問: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "啓動程序", "skipFiles": ["<node_internals>/**"], "program": "${workspaceFolder}/api.js" } ] }
修改對應的 program 的 value 的文件爲執行的入口文件便可。(這裏推薦使用)
經過 node --inspect index.js 啓動服務,則能夠在 chrome 瀏覽器控制檯看到對應的 node 的小圖標,點擊而後就有一個對應的小彈框進行 debug 啦。試了下,也推薦吧,哈哈,看我的喜愛了。
看完整個koa2 的api,以及使用了一些特性以後,咱們不難發現,koa2 相對於 express 真的要簡潔不少,其核心也在於洋蔥圖和中間件的機制,那麼可以編寫中間件和從茫茫大海中找到高可用的中間件很是重要,這二點是你們將來須要注意的地方。過完年了,本身身爲湖北人,由於此次肺炎沒能回到老家過年,那就讓本身多學習一些知識吧~ 同時也但願此次的疫情能夠快速的被消滅掉~奧利給!!!