《前端之路》--- 重溫 Koa2

[TOC]javascript

1、簡單介紹

1.一、快速開始 (這裏省略了安裝的過程)

const Koa = require('koa')
const app = new Koa()

app.use( async ( ctx ) => {
  ctx.body = 'hello koa2'
})

app.listen(3000)
複製代碼

1.二、源碼簡單解析

源碼文件主要包含了 application.js 、context.js 、request.js 、response.jscss

  • application.js 是 Koa 的入口文件封裝了 ctx、request、response, 以及核心的中間件處理流程
  • context.js 處理應用上下文,裏面直接封裝部分request.js和response.js的方法
  • request.js 處理http請求
  • response.js 處理http響應

1.三、中間件的簡單開發

這裏主要介紹如何使用 async/await 在 koa2 中進行中間件的開發html

middleware 在 koa2 中如何使用前端

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 中間件java

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
複製代碼

2、 路由

原生 JS 實現 koa 的 routernode

通過思考🤔, 實現路由的基本原理: 經過請求進來的 url 匹配到對應的頁面文件,而後經過 fs 讀取對應文件的內容,並返回給 ctx.body, 那下面咱們就按照這個思路來實現一下路由。mysql

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 中間件 
複製代碼

3、請求數據

3.一、 GET 請求數據獲取

GET 請求數據獲取的方法有2中,以下git

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
    };
});
複製代碼

返回結果github

url: "/index?page=1"
req_query: {page: "1"}
req_queryString: "page=1"
ctx_query: {page: "1"}
ctx_queryString: "page=1"
複製代碼

疑惑🤔的 點: 從上線文中獲取的request對象和直接經過上線文獲取的參數 有什麼區別? 爲何要這麼設計?sql

  • 從 Koa2 的框架設計層面 app.js 中封裝了 ctx、request、response
  • 從 Koa2 的框架設計層面 ctx.js 中封裝了 request、response 方法
  • 從上下文中獲取和從 ctx.request 獲取的參數是同樣的,由於底層方法是一致的
  • 直接從上下文中獲取的方式簡單、快捷
  • 從上下文中的 request 對象中獲取的話,會更加的明確該屬性來源,不容易混淆。

注意:ctx.request是context通過封裝的請求對象,ctx.req是context提供的node.js原生HTTP請求對象, 和這裏的 ctx.query 和 ctx.request.query 是沒有關係的。

3.二、 POST 請求數據獲取

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>';
    }
複製代碼

3.三、 koa-bodyparser中間件

其實是封裝了一層 post 的數據處理方法,而後將其賦值給了 ctx.request 的 body 屬性

const bodyParser = require('koa-bodyparser')

// 使用ctx.body解析中間件
app.use(bodyParser())

// 處理 method 爲 POST 的方法
let postData = ctx.request.body
ctx.body = postData

複製代碼

4、 靜態資源加載

4.一、靜態資源加載源碼解析

// 核心代碼
│   ├── 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 );
// 疑惑的點: 爲何須要把文件名稱也加上呢? 你們也能夠做爲一個思考
複製代碼

5、 Koa2 使用 cookie/session

5.一、koa2 使用 cookie

簡單粗暴的直接上代碼吧, 裏面有一些須要注意的問題點,都在註釋點中了。關鍵點就在與 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';
    }
});
複製代碼

5.二、koa2 使用 session

這裏須要注意下,koa 自己沒有提供 session 的方法,這裏的例子是經過中間件來實現一些你須要的能力。這裏的兩種實現 session 能力的方案。這兩個方案的區別就在於 存儲信息的大小。

5.2.一、 經過 koa-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 來判斷當前用戶是否登錄成功、是否在有效期內等等
複製代碼
5.2.二、 經過 koa-mysql-session 和 koa-session-minimal 將信息存儲在 mysql 中
// 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: ''
};

複製代碼

6、 koa2加載模板引擎

6.一、 koa2 加載模板引擎 (ejs)

這裏直接展現使用的 demo

app.use(
    views(path.join(__dirname, './ejs'), {
        extension: 'ejs'
    })
);

app.use(async ctx => {
    let title = 'hello 404';
    await ctx.render('404', {
        title
    });
});
複製代碼

另外,咱們附上 ejs 官方文檔

7、 koa2 中簡單使用 mysql 數據庫

這裏寫了一個簡單的 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 最新使用文檔

8、 koa2 中使用單元檢測

這裏的單元測試主要是正對 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();
            });
    });
});

// 這裏發現,咱們在測試咱們的藉口返回數據的類型、數值、錯誤碼等類型的時候會有很是大的幫助的。之後若是須要用起來的話,推薦使用之。
複製代碼

9、 node 服務端開發過程當中的 開發 debug 方式

9.一、vscode 進行debug

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 的文件爲執行的入口文件便可。(這裏推薦使用)

9.二、chrome 瀏覽器進行 debug

經過 node --inspect index.js 啓動服務,則能夠在 chrome 瀏覽器控制檯看到對應的 node 的小圖標,點擊而後就有一個對應的小彈框進行 debug 啦。試了下,也推薦吧,哈哈,看我的喜愛了。

10、總結

看完整個koa2 的api,以及使用了一些特性以後,咱們不難發現,koa2 相對於 express 真的要簡潔不少,其核心也在於洋蔥圖和中間件的機制,那麼可以編寫中間件和從茫茫大海中找到高可用的中間件很是重要,這二點是你們將來須要注意的地方。過完年了,本身身爲湖北人,由於此次肺炎沒能回到老家過年,那就讓本身多學習一些知識吧~ 同時也但願此次的疫情能夠快速的被消滅掉~奧利給!!!

GitHub 地址:(歡迎 star 、歡迎推薦 : ) 《前端之路》 - 重溫Koa2

相關文章
相關標籤/搜索