前言
如果不使用koa,用原生的node來建立一個簡單的 HTTP 伺服器,並定義埠號為3000
const http = require('http') const server = http.createServer((req, res) => { res.end('hello world') }) server.listen(3000, () => { console.log('server is running on 3000 port'); })
接下來我們用對此進行二次封裝的KOA來建立這個demo
KOA
“Koa”通常指的是 Koa 框架,它是一個基於 Node.js 的 Web 應用框架,由 Express 原班人馬打造。
Koa 致力於成為一個更小、更富有表現力、更健壯的 Web 框架。它使用 async/await 語法來處理非同步流程,使得程式碼更加簡潔和易讀。
Koa 具有輕量、靈活、中介軟體機制等特點,能夠幫助開發者更高效地構建 Node.js 應用。
首先安裝依賴
npm i koa
小試牛刀
const Koa = require('koa'); const app = new Koa(); app.listen(3000, () => { console.log('server is running at port 3000'); });
能夠看出來其實這就是對剛剛的程式碼做了一個封裝。接下來我們建立一個函式體,然後用app給use掉,攜帶koa的上下文物件context
,事實上他整合了我們最初始的res和req兩個物件。
const Koa = require('koa'); const app = new Koa(); const main = (ctx) => { console.log(ctx); } app.use(main); app.listen(3000, () => { console.log('server is running at port 3000'); });
const main = (ctx) => { console.log(ctx); }
:定義了一個名為main
的中介軟體函式,它接收一個ctx
引數,即上下文物件,並將其列印到控制檯。app.use(main);
:使用app.use
方法註冊中介軟體函式main
,使其在每次請求處理時被呼叫。
ctx
ctx
是 Koa 中的上下文(Context)物件,它封裝了 Node.js 中的原生 req
(請求)和 res
(響應)物件。可以將 ctx
看做是一次 HTTP 請求和響應過程中的相關資訊的集合。
透過 ctx
,開發者可以方便地訪問和操作請求及響應的各種屬性和方法。
ctx
物件具有以下主要屬性:
ctx.req
:原生的req
物件。ctx.res
:原生的res
物件。ctx.request
:Koa 自己封裝的請求物件,該物件不僅包含原生req
物件的屬性,還有一些額外的便捷方法和屬性。例如,可以更方便地獲取查詢引數(ctx.query
)、解析 URL(ctx.path
)等。ctx.response
:Koa 自己封裝的響應物件,同樣具有一些額外的方法和屬性,方便設定響應的相關資訊。ctx
本身還代理了ctx.request
和ctx.response
身上的屬性,這意味著可以直接透過ctx
來訪問ctx.request
和ctx.response
的部分屬性,例如直接使用ctx.query
來獲取查詢引數,而無需使用ctx.request.query
。
我們把ctx列印出來是這樣的:
{ request: { method: 'GET', url: '/', header: { host: 'localhost:3000', connection: 'keep-alive', 'cache-control': 'max-age=0', 'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Microsoft Edge";v="126"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', 'upgrade-insecure-requests': '1', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0', accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'sec-fetch-site': 'none', 'sec-fetch-mode': 'navigate', 'sec-fetch-user': '?1', 'sec-fetch-dest': 'document', 'accept-encoding': 'gzip, deflate, br, zstd', 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6' } }, response: { status: 404, message: 'Not Found', header: [Object: null prototype] {} }, app: { subdomainOffset: 2, proxy: false, env: 'development' }, originalUrl: '/', req: '<original node req>', res: '<original node res>', socket: '<original node socket>' }
因此此時我們想向前端響應一個hello world,甚至可以像最開始那樣,直接使用他封裝好了的ctx裡面的res.end。
const main = (ctx) => { // console.log(ctx); ctx.res.end('hello koa'); }
除此之外,我們還可以給response響應體上掛一個body屬性
const main = (ctx) => { // console.log(ctx); // ctx.res.end('hello koa'); ctx.response.body = 'hello koa'; } app.use(main);
但是這麼寫屬實是比原生更加麻煩了,因此koa做了一個代理,允許我們直接省略response
const main = (ctx) => { // console.log(ctx); // ctx.res.end('hello koa'); ctx.body = 'hello koa'; } app.use(main);`js
同理,想獲取url,就可以直接使用原生的req.url也可以用封裝好的,再直接可以省略。
const main = (ctx) => { // console.log(ctx); // ctx.res.end('hello koa'); // ctx.response.body = 'hello koa'; ctx.body = 'hello koa'; console.log(ctx.req.url); console.log(ctx.request.url); console.log(ctx.url); } app.use(main);
不由感嘆,開源的力量,不用大家重複造輪子,這些封裝都很優雅,支援各種寫法,幫助大家省了很多麻煩。
我們來看看,koa還封裝了個accepts
const main = (ctx) => { if (ctx.request.header.accept === 'xml') { ctx.body = '<data>hello xml </data>'; } else if (ctx.request.accepts('html')) { ctx.body = '<p>hello html</p>'; } } app.use(main);
我們發現原生的拿accept無法匹配到,除非把請求頭全部拿過來(text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.7)
為此koa封裝了ctx.request.accepts方法,可以直接匹配。
如果我們想讀取一個html檔案然後給他響應到瀏覽器上
const fs = require('fs'); const Koa = require('koa'); const app = new Koa(); const main = (ctx) => { ctx.response.type = 'text/html'; const context = fs.readFileSync('./template.html', 'utf-8') //toString() console.log(context); ctx.body = context; } app.use(main); app.listen(3000, () => { console.log('server is running at port 3000'); });
要讓瀏覽器讀得懂,要設定響應頭,告訴瀏覽器這是一個html,如果沒有toString或者沒有設定utf8編碼,那麼會拿到一個buffer流,如果響應一個buffer流就會自動觸發下載,因此此時讓你去做一個觸發下載某個東西的操作你也可以做了。
我們能夠理解,這裏ctx.response.type可以直接省略response了。
接下來再換一種方式來寫
const fs = require('fs'); const Koa = require('koa'); const app = new Koa(); const main = (ctx) => { // ctx.response.type = 'text/html'; // const context = fs.readFileSync('./template.html', 'utf-8') //toString() // console.log(context); // ctx.body = context; // ctx.response.type = 'text/html'; ctx.res.writeHead(200,{'Content-type':'text/html'}) // 原生的方式不愛用就用上面的 const content = fs.createReadStream('./template.html'); console.log(content); ctx.body = content; } app.use(main); app.listen(3000, () => { console.log('server is running at port 3000'); });
koa路由
用最開始的辦法寫路由
const fs = require('fs'); const Koa = require('koa'); const app = new Koa(); const main = (ctx) => { if (ctx.url === '/') { ctx.type = 'text/html'; ctx.body = '<h2>home</h2>' }else{ ctx.type = 'text/html'; ctx.body = '<a href="/">go home</a>' } }
可以看出,這種寫法也是相當的噁心。因此我們使用koa-route,因為這個框架非常簡單,因此很多團隊都可以自己做封裝,大家可以自行選擇使用什麼koa-route/koa-router...
安裝依賴
npm i koa-route
小試牛刀
const router = require('koa-route'); const Koa = require('koa'); const app = new Koa(); const main = (ctx) => { ctx.type = 'html'; ctx.body = '<h2>home</h2>' } const about = (ctx) => { ctx.type = 'html'; ctx.body = '<a href="/">about,go home</a>' } app.use(router.get('/', main)) app.use(router.get('/about', about)) // app.use(main); app.listen(3000, () => { console.log('server is running at port 3000'); });
注意:事實上koa只能use一個,這裏的路由能use兩個是因為封裝過了,此時我們自己寫一個列印日誌方法如果不寫next,後面的use就無法使用,因此:
const router = require('koa-route'); const Koa = require('koa'); const app = new Koa(); const main = (ctx) => { ctx.type = 'html'; ctx.body = '<h2>home</h2>' } const about = (ctx) => { ctx.type = 'html'; ctx.body = '<a href="/">about,go home</a>' } const logger = (ctx, next) => { console.log(`${ctx.method} - ${ctx.url} - ${Date.now()}`); next(); } app.use(logger) app.use(router.get('/', main)) app.use(router.get('/about', about)) // app.use(main); app.listen(3000, () => { console.log('server is running at port 3000'); });
next
const Koa = require('koa'); const app = new Koa(); const one = (next) =>{ // 中介軟體 console.log(1); console.log(2); } const two = () => { console.log(3); console.log(4); } const three = () => { console.log(5); console.log(6); } app.use(one); app.use(two); app.use(three); app.listen(3000, () => { console.log('server is running at port 3000'); });
如果是這樣一份程式碼,勢必只會列印1,2,因此要透過next的呼叫進入下一個中介軟體,一碰到next就會進入下一個中介軟體,如果走完了,就會回頭走沒走完的。
const Koa = require('koa'); const app = new Koa(); const one = (next) =>{ // 中介軟體 console.log(1); next() console.log(2); } const two = (next) => { console.log(3); next() console.log(4); } const three = () => { console.log(5); console.log(6); } app.use(one); app.use(two); app.use(three); app.listen(3000, () => { console.log('server is running at port 3000'); });
因此,答案勢必是:1,3,5,6,4,2,看得出來,這底層一定是用遞迴
寫的。不難理解,koa的中介軟體執行過程就是個洋蔥模型,用一根筷子插入一個洋蔥,進入第一層,第二層,第三層,然後到了中間,繼續深入就回到了第二層,第一層。
小結
“Koa”是一個基於 Node.js 的 Web 應用框架,它以簡潔、高效和靈活著稱。
本文介紹了一下Koa的基本使用方法,以及其中的一些細節內容,ctx包括什麼東西,next怎麼使用,如何使用koa做路由...
Koa主要特點包括:
基於 async/await :使用 ES6 的非同步函式語法,讓非同步流程的控制更加直觀和簡潔。
輕量級:核心模組非常小,只包含最基本的功能,開發者可以根據需求選擇和新增擴充套件。
中介軟體機制:透過
app.use
方法來新增中介軟體,中介軟體可以按照新增的順序依次執行,方便對請求和響應進行處理和修改。