Koa框架學習
Koa 是一個新的 web 框架,由 Express 幕後的原班人馬打造, 致力於成為 web 應用和 API 開發領域中的一個更小、更富有表現力、更健壯的基石。 通過利用 async 函式,Koa 幫你丟棄回撥函式,並有力地增強錯誤處理。 Koa 並沒有捆綁任何中介軟體, 而是提供了一套優雅的方法,幫助您快速而愉快地編寫服務端應用程式。
以上是Koa官網對Koa框架的描述。
1️⃣概述
1.1❀ Koa簡介
- Koa核心模組並未捆綁任何中介軟體(路由功能也需要引入別的中介軟體)【方便使用者的擴充】
- Koa使用了 Promise、async/await 語法來進行非同步程式設計(Express 是基於事件和回撥的)【避免地獄回撥】
- Koa增強了對錯誤的處理
- Koa開發的web應用體積更小,功能更強大
可見, Koa框架和 Expres框架的主要差別在於非同步程式設計和中介軟體方面,其他特性是相似的。
由於Koa進行非同步呼叫時強制使用async/await,因此需要將非同步回撥方法轉換為Promise,為了避免每個回撥方法都需要自己包裝,接下來將介紹這一問題目前最好的解決方案-Bluebird.
1.2❀ Bluebird
Bluebird 是Node.js最出名的 Promise 實現,除了實現標準的Promise規範之外,Bluebird還提供了包裝方法,可以快速地將Node.js回撥風格的函式包裝為Promise。
安裝:
npm install bluebird --save
將Node.js 回撥風格的函式包裝為Promise函式,該方法簽名如下:
bluebird.promisifyAll(target[,options])
target
需要包裝的物件。
- target為普通物件,則包裝後生成的非同步API只有該物件持有
- target為原型物件,則包裝後生成的非同步API被該原型所有例項持有。
options
- suffix:設定非同步API方法名字尾,預設為“Async”
- multiArgs:是否允許多個回撥引數,預設false。Promise的then()方法只接受一個resolve類引數(reject類也可以接受一個),但Node.js的回撥函式
function(err,...data)
卻可以接受多個引數。multiArgs為true時,bluebird將回撥函式的所有引數組裝成一個陣列,然後傳遞給then,從而得到多個引數。
bluebird.promisifyAll()
只會給目標物件新增新方法,原來的 Node.js 回撥風格的方法不受影響。
包裝之後的方法和包裝之前的方法使用起來只有一個差別,那就是不要傳遞迴調函式,通過 Promise 獲取結果。
??以下是包裝fs物件的例項??
const fs = require("fs");
const bluebird = require("bluebird");
bluebird.promisifyAll(fs);
//回撥函式示例
fs.readFile("./package.json", { encoding: "utf-8" }, (err, data) => {
if (err) {
console.warn("讀取異常", err);
return;
}
console.log(data);
});
//Prmise示例
fs.readFileAsync("./package.json", { encoding: "utf-8" })
.then((data) => {
console.log(data);
})
.catch((err) => {
console.warn("讀取異常", err);
});
2️⃣Hello Koa
1.初始化專案
npm init -y
2.模組安裝
npm i webpack koa --save
3.編碼
const Koa = require("koa");
const app = new Koa();//不同於express Koa是new例項
app.use(async (context) => {
context.body = "Hello Koa";
});
app.listen(8080, () => {
console.log("listen on 8080");
});
4.執行
node .\app.js
Koa核心模組不繫結其它中介軟體,例子沒有使用到路由,而是使用了中介軟體,無論任何對該服務的請求都只會返回Hello Koa
3️⃣Context
Koa Context包含:Koa Request、Koa Response和應用例項(app)等
Koa Request 物件是在 node 的 原生請求物件之上的抽象
Koa Response 物件是在 node 的原生響應物件之上的抽象
注意幾個容易混淆API:
ctx.req
:Node 的 request 物件.
ctx.res
:Node 的 response 物件.
- 繞過 Koa 的 response 處理是 不被支援的. 應避免使用以下 node 屬性:
res.statusCode
res.writeHead()
res.write()
res.end()
ctx.request
:koa 的 Request 物件.
ctx.response
:koa 的 Response 物件.
3.1❀ Request 和 Response 的別名
一般不直接通過ctx.request.[propName]/ctx.response.[propName]
呼叫Koa Request/Response物件屬性,而是通過對應的別名來呼叫其屬性,比如:ctx.headers
就是ctx.request.headers
的別名,就是簡寫一層呼叫。
那為什麼ctx.headers
是ctx.request.headers
,而不是ctx.response.headers
呢?
以下是我從官網目前版本擷取的別名欄位:?官網Koa 別名
可以看到,ctx.[propName]在Request和Response別名裡,沒有重複的propName。說明Koa對Context維護了一個唯一鍵名指向對應Koa Request和Response物件屬性名的資料結構。
ctx.headers==ctx.request.headers
ctx.body==ctx.response.body
假如想要Koa Response物件的headers怎麼辦?
那就不使用別名,直接讀取即可 ctx.response.headers
使用別名,這不是很容易混淆嗎?
我也是這麼覺得的!估計初學者都是如此。但存在肯定有它的意義,別名歸屬(Request/Response)的劃分,可能更傾向於該屬性在伺服器中的使用頻度。以headers為例,雖然請求物件和響應物件都可以用到這個屬性,但是對於伺服器而言,人們可能更關注的是請求頭的資訊而非響應頭的資訊。但對於熟悉Koa的人來說,通過別名,簡寫也是一種效率的提升。這是我個人的一點看法。
3.2❀ Context常用方法和屬性
只是羅列Conetxt常用的方法和屬性,想看更全面的和具體用法?官網Context
-
ctx.request:Koa的請求物件,一般不直接使用,通過別名引用來訪問。
-
ctx.response:Koa的響應物件,一般不直接使用,通過別名引用來訪問。
-
ctx.state
:自定義資料儲存,比如中介軟體需要往請求中掛載變數就可以存放在ctx.state中,後續中介軟體可以讀取。
前面的中介軟體 ctx.state.username=‘xx’ ,執行next()後,後面的中介軟體可以通過ctx.state.username獲取到xx -
ctx.throw()
:丟擲 HTTP異常。 -
ctx.headers:請求報頭,ctx.request.headers的別名。
-
ctx.method
:請求方法,ctx.request.method的別名。 -
ctx.url
:請求連結,ctx.request.url的別名。 -
ctx.path
:請求路徑,ctx.request.path的別名。 -
ctx.query
:解析後的GET引數物件,ctx.request.query的別名。 -
ctx.host:當前域名,ctx.request.host的別名。
-
ctx.ip:客戶端IP,ctx.request.ip的別名。
-
ctx.ips:反向代理環境下的客戶端IP列表,ctx.request.ips的別名。
-
ctx.get():讀取請求報頭,ctx.request.get的別名。
-
ctx.body
:響應內容,支援字串、物件、Buffer,ctx.response.body的別名。 -
ctx.status
:響應狀態碼,ctx.response.status的別名。 -
ctx.type:響應體型別,ctx.response.type 的別名。
-
ctx.redirect()
:重定向,ctx.response.redirect的別名。 -
ctx.set()
:設定響應報頭,ctx.response.set的別名。
??顯示當前請求頭資訊並新增自定義報文頭??
const Koa = require("koa");
const app = new Koa();
app.use(async (ctx) => {
ctx.set("customer-header", "nothing");
ctx.body = {
method: ctx.method,
path: ctx.path,
url: ctx.url,
query: ctx.query,
headers: ctx.headers,
respHeaders: ctx.response.headers,
};
});
app.listen(8080, () => {
console.log("listen on 8080");
});
4️⃣Cookie
Cookie是Web應用維持少量資料的一種手段,常通過Cookie來維持服務端與客戶端使用者的身份認證。
4.1❀ Cookie 簽名
由於 Cookie 存放在瀏覽器端,存在篡改風險,因此Web應用一般會在存放cookie資料的時候同時存放一個簽名 cookie,以保證 Cookie 內容不被篡改。配置了cookie簽名,一旦cookie被改動,那麼該cookie直接會被瀏覽器移除。
Koa中需要配置 Cookie 簽名金鑰才能使用 Cookie功能,否則將報錯。
app.keys=['signedkey'];//這是自定義金鑰,你可以寫任何字串,推薦使用隨機字串,開啟簽名後會根據金鑰使用加密演算法加密該值
4.2❀ 寫入Cookie
const Koa = require("koa");
const app = new Koa();
app.keys = ["signedkey"];
app.use(async (ctx) => {
ctx.cookies.set("logged", 1, {
signed: true,//啟用cookie簽名
httpOnly: true,
maxAge: 3600 * 10,
});
ctx.body = "ok";
});
app.listen(8080);
cookie中不僅存在logged還有logged.sid,這個logged.sid就繫結的簽名。伺服器讀取logged的時候還會讀取logged.sid,一旦發現二者不匹配,設定cookie未undefined.
4.3❀ 讀取Cookie
const Koa = require("koa");
const app = new Koa();
app.keys = ["signedkey"];
app.use(async (ctx) => {
const logged=ctx.cookies.get("logged", {
signed: true,
});
ctx.body = logged;
});
app.listen(8080);
5️⃣中介軟體
5.1❀ 概念
類似Express中介軟體,可以訪問請求物件、響應物件 和 next函式。只不過Koa的中介軟體通過操作Context物件獲取請求物件、響應物件。
如果一個請求流程中,任何中介軟體都沒有輸出響應,Koa 中此次請求將返回404狀態碼(在Express中會將請求掛起直至超時)。
造成這種差別的原因是Express 需要手動執行輸出函式才可以結束請求流程,而Koa 使用了async/await來進行非同步程式設計,不需要執行回撥函式,直接對ctx.body賦值即可(如果連body都沒有,相當於資源不存在,自然404)。
Koa的中介軟體是一個標準的非同步函式,函式簽名如下:
async function middleware(ctx, next) //next函式:指向下一個中介軟體。
執行完邏輯程式碼,將需要傳遞的資料掛載到ctx.state,並且呼叫 await next()才能將請求交給下一個中介軟體處理。
5.2❀ 洋蔥模型 (中介軟體執行流程)
Koa的中介軟體模型稱為“洋蔥圈模型”,請求從左邊進入,有序地經過中介軟體處理,最終從右邊輸出響應。
最先use的中介軟體在最外層,最後use的中介軟體在最內層。
一般的中介軟體會執行兩次,呼叫next之前為第一次,也就是“洋蔥左半邊”這一部分,從外層向內層依次執行。當後續沒有中介軟體時,就進入響應流程,也就是“洋蔥右半邊”這一部分,從內層向外層依次執行,這是第二次執行。
??洋蔥圈模型??
const Koa = require("koa");
const app = new Koa();
async function middleware1(ctx,next){
console.log('m1 start');
await next();
console.log('m1 end');
}
async function middleware2(ctx,next){
console.log('m2 start');
await next();
console.log('m2 end');
}
app.use(middleware1);
app.use(middleware2);
app.use(async (ctx)=>{
console.log('我是路由,我後面沒有中介軟體了');
ctx.body='洋蔥圈模型';
})
app.listen(8080);
類似棧幀,多了路由。
6️⃣錯誤處理
Koa採用了洋蔥圈模型,所以Koa的錯誤處理中介軟體需要在應用的開始處掛載,這樣才能將整個請求-響應週期涵蓋,捕獲其發生的錯誤。而Express錯誤處理中介軟體需要放置在應用末尾。
??多個錯誤處理器(先處理後記錄)??
const Koa = require("koa");
const app = new Koa();
async function errorHandler(ctx,next){
try{
await next();
}catch(e){
ctx.status=e.status||500;
ctx.body='Error:'+e.message;
}
}
async function errorLogger(ctx,next){
try{
await next();
}catch(e){
console.log(`${ctx.method} ${ctx.path} Error:${e.message}`);
throw e;//繼續往外丟擲錯與,被errorHandler接收處理
}
}
app.use(errorHandler);
app.use(errorLogger);
app.use((ctx)=>{
ctx.throw(403,'Forbidden');
})
app.listen(8080);
7️⃣路由模組
7.1❀ 預設路由函式
Koa核心並沒有提供路由功能,但是可以使用一個預設的路由函式來提供響應。所有的請求都會執行該預設的路由函式。
路由函式的定義如下:
async function(ctx,next)
如果路由函式內部未使用非同步邏輯,async是可以省略的。一個路由可以有多個處理函式。
const Koa = require("koa");
const app = new Koa();
app.use(async (ctx,next)=>{//預設路由函式1
ctx.body=1;
next();//沒有next就不會執行下一個路由函式了
});
app.use((ctx)=>{//預設路由函式2
ctx.body=2;
});
app.listen(8080,()=>{
console.log('listen on 8080');
})
這個並不算真正路由功能,下面介紹koa-router.
7.2❀ Hello koa-router
npm i koa-router --save
const Koa = require("koa");
const Router=require('koa-router');
const app = new Koa();
const router=new Router();
router.get('/',async (ctx)=>{
ctx.body='root page';
})
router.post('/user/:userId(\\d+)',ctx=>{//限定路由引數為數值
ctx.body=ctx.path;
})
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(8080,()=>{
console.log('listen on 8080');
})
allowedMethods是當所有路由中介軟體執行完成之後,若ctx.status為空或者404的時候,豐富response物件的header頭。當然,如果我們不設定router.allowedMethods()在表現上除了ctx.status不會自動設定,以及response header中不會加上Allow之外,不會造成其他影響.
7.3❀ 路由物件
路由需要例項化後才能配置和掛載。
路由構造器:
function Router([options]) //常用的option有 prefix,指定路由字首
路由定義方法:
router.method(path,handler);//支援多個handler
7.4❀ 路由函式
Koa-router的路由函式與預設路由函式相似,支援多個路由函式處理同一個請求。
router.get(
"/",
async (ctx, next) => {
ctx.state.data = "root page";
await next();
},
(ctx) => {
ctx.body = ctx.state.data;
}
);
7.5❀ 路由級別中介軟體
Koa預設的中介軟體是應用級別的,所有請求都被中介軟體處理。因為Koa-router支援多個路由函式,因此可以在指定路由或者整個路由物件上(常應用於模組化路由)使用中介軟體。
async function logger(ctx,next){
console.log(`${ctx.method} ${ctx.path} `);
await next();
}
router.get('/',logger,ctx=>{
ctx.body='hello';
})
7.6❀ 模組化路由
將路由拆分在對應業務模組,方便維護!
- 獨立檔案實現路由邏輯
- 入口檔案掛載路由
user.js
const Router = require("koa-router");
const router = new Router({prefix:'/user'});
router.get('/',ctx=>{
ctx.body='user Page';
})
router.post('/login',async (ctx)=>{
ctx.body='login';
})
module.exports=router;
sites.js
const Router = require("koa-router");
const router = new Router();
router.get('/',ctx=>{
ctx.body='index Page';
})
router.get('/about',ctx=>{
ctx.body='about Page';
})
module.exports=router;
app.js
const Koa = require("koa");
const Router = require("koa-router");
const app = new Koa();
const user=require('./user');
const sites=require('./sites');
app.use(user.routes()).use(user.allowedMethods());
app.use(sites.routes()).use(sites.allowedMethods());
app.listen(8080,()=>{
console.log('listen on 8080');
})
8️⃣模板渲染
支援很多模板,暫時使用ejs模板
8.1❀ koa-ejs
koe-ejs支援ctx.state,掛載到ctx.state中的變數可以直接在ejs模板中使用。
安裝koa-ejs模組
npm i koa-ejs --save
templates/home.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %> </title>
</head>
<body>
<%= name %>
</body>
</html>
app.js
const Koa = require("koa");
const ejsRender =require('koa-ejs');
const app = new Koa();
ejsRender(app,{//使用ejs外掛
root:'./templates',//模板目錄
layout:false,//關閉模板佈局
viewExt:'ejs'//使用ejs模板引擎
})
app.use(async (ctx)=>{
ctx.state.title='首頁';
await ctx.render('home',{//給home模板頁傳遞屬性
name:'Joe'
})
});
app.listen(8080, () => {
console.log("listen on 8080");
});
8.2❀ 模板佈局
Web頁面一般由一下結構組成:
- 頭部區域(導航欄等)
- 內容主體
- 底部區域(版權宣告等)
一般而言,頭部和底部區域不變化,只有內容主體變化,那就以內容主體構建佈局!
templates/main.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>main 頁面</title>
</head>
<body>
<header style="border: 1px solid black;">頁頭</header>
<%- body %>
<footer style="border: 1px solid black;">頁尾</footer>
</body>
</html>
templates/home.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %> </title>
</head>
<body>
<%= name %>
</body>
</html>
app.js
const Koa = require("koa");
const ejsRender =require('koa-ejs');
const app = new Koa();
ejsRender(app,{//使用ejs外掛
root:'./templates',//模板目錄
layout:'main',//開啟模板佈局 以main模板作為佈局模板
viewExt:'ejs'//使用ejs模板引擎
})
app.use(async (ctx)=>{
ctx.state.title='主頁';
await ctx.render('home',{//指定home模板作為子模板 之後父模板 <%- body %> body就是home模板的body節點
name:'Joe'
})
});
app.listen(8080, () => {
console.log("listen on 8080");
});
相關文章
- 學習Koa
- 新一代web框架Koa原始碼學習Web框架原始碼
- koa+mongodb學習MongoDB
- <node.js學習筆記(5)>koa框架和簡單爬蟲練習Node.js筆記框架爬蟲
- koa2學習筆記筆記
- koa框架會用也會寫—(koa-view、koa-static)框架View
- Koa2 原始碼學習(下)原始碼
- Koa2 原始碼學習(上)原始碼
- 駁 《駁 《駁 《駁 《停止學習框架》》》》、《駁 《駁 《停止學習框架》》》、《駁 《停止學習框架》》、《停止學習框架》框架
- koa框架會用也會寫—(koa-router)框架
- koa框架會用也會寫—(koa-bodyparser、koa-better-body)框架
- 深度學習學習框架深度學習框架
- 一個學習 Koa 原始碼的例子原始碼
- Koa2進階學習筆記筆記
- koa框架會用也會寫—(koa的實現)框架
- OS 學習框架框架
- PySpider框架學習IDE框架
- Httprunner框架學習HTTP框架
- 停止學習框架框架
- Hibernate框架學習框架
- 2020-2-26-koa框架使用框架
- Koa 框架常用知識點整理框架
- 深度學習框架Pytorch學習筆記深度學習框架PyTorch筆記
- 日誌框架學習框架
- 駁 《停止學習框架》框架
- Hugo-框架學習Go框架
- RPC框架-hessian學習RPC框架
- Java集合框架學習Java框架
- koa2框架的使用與解析框架
- 前端全棧必會node框架koa。。。前端全棧框架
- 學習Koa – 讓我們寫一箇中介軟體
- 學習Koa - 讓我們寫一箇中介軟體
- Netty 框架學習 —— 編解碼器框架Netty框架
- dubbo框架設計學習框架
- Bootstrap框架:學習筆記boot框架筆記
- MYSQL學習(二) --MYSQL框架MySql框架
- Mybatis框架 入門學習MyBatis框架
- Netty 框架學習 —— 傳輸Netty框架