1笋鄙、 Koa初體驗(yàn)
1.1. 認(rèn)識(shí)Koa
Koa官方的介紹:
- koa:next generation web framework for node.js畅姊;
- koa:node.js的下一代web框架顷级;
事實(shí)上颠区,koa和express是同一個(gè)團(tuán)隊(duì)開發(fā)的一個(gè)新的Web框架:
- 目前團(tuán)隊(duì)的核心開發(fā)者TJ的主要精力也在維護(hù)Koa昨寞,express已經(jīng)交給團(tuán)隊(duì)維護(hù)了坡贺;
- Koa旨在為Web應(yīng)用程序和API提供更小、更豐富和更強(qiáng)大的能力翘瓮;
- koa相對(duì)于express具有更強(qiáng)的異步處理能力
- Koa的核心代碼只有1600+行贮折,是一個(gè)更加輕量級(jí)的框架,我們可以根據(jù)需要安裝和使用中間件资盅;
1.2. koa初體驗(yàn)
koa與express的基本開發(fā)模式是比較相似的
// koa 的Web服務(wù)器
const Koa = require('koa');
const app = new Koa();
app.use((ctx, next) => {
console.log("middleware 01");
next();
})
app.use((ctx, next) => {
console.log("middleware 02");
ctx.response.body = "Hello World";
})
app.listen(8000, () => {
console.log("服務(wù)器啟動(dòng)成功~");
});
koa注冊(cè)的中間件提供了兩個(gè)參數(shù):
- ctx:上下文(Context)對(duì)象调榄;
- koa并沒有像express一樣,將req和res分開呵扛,而是將它們作為ctx的屬性每庆;
- ctx代表每次請(qǐng)求的上下文對(duì)象;
- ctx.request:獲取請(qǐng)求對(duì)象今穿;
- ctx.response:獲取響應(yīng)對(duì)象缤灵;
- next:本質(zhì)上是一個(gè)dispatch,類似于express的next蓝晒;
koa通過創(chuàng)建的app對(duì)象腮出,注冊(cè)中間件只能通過use方法:
- Koa并沒有提供methods的方式來(lái)注冊(cè)中間件;
- 也沒有提供path中間件來(lái)匹配路徑芝薇;
但是真實(shí)開發(fā)中我們?nèi)绾螌⒙窂胶蚼ethod分離呢胚嘲?
- 方式一:根據(jù)request自己來(lái)判斷;
- 方式二:使用第三方路由中間件洛二;
// 方式一:根據(jù)request自己判斷
app.use((ctx, next) => {
if (ctx.request.path === '/users') {
if (ctx.request.method === 'POST') {
ctx.response.body = "Create User Success~";
} else {
ctx.response.body = "Users List~";
}
} else {
ctx.response.body = "Other Request Response";
}
})
整個(gè)代碼的邏輯是非常復(fù)雜和混亂的馋劈,真實(shí)開發(fā)中我們會(huì)使用路由立倍。
1.3. 路由的使用
koa官方并沒有給我們提供路由的庫(kù),我們可以選擇第三方庫(kù):koa-router
安裝koa-router
npm install koa-router
koa-router基本使用
// user.router.js
const Router = require('koa-router');
const userRouter = new Router();
userRouter.get('/users', (ctx, next) => {
ctx.response.body = "user list~";
});
userRouter.post('/users', (ctx, next) => {
ctx.response.body = "create user info~";
});
module.exports = userRouter;
在app中將router.routes()注冊(cè)為中間件:
app.use(userRouter.routes());
app.use(userRouter.allowedMethods());
注意 : allowedMethods用于判斷某一個(gè)method是否支持:
- 如果我們請(qǐng)求 get侣滩,那么是正常的請(qǐng)求口注,因?yàn)槲覀冇袑?shí)現(xiàn)get;
- 如果我們請(qǐng)求 put君珠、delete寝志、patch,那么就自動(dòng)報(bào)錯(cuò):Method Not Allowed策添,狀態(tài)碼:405材部;
- 如果我們請(qǐng)求 link、copy唯竹、lock乐导,那么就自動(dòng)報(bào)錯(cuò):Not Implemented,狀態(tài)碼:501浸颓;.
router的前綴
通常一個(gè)路由對(duì)象是對(duì)一組相似路徑的封裝物臂,那么路徑的前綴都是一直的,所以我們可以直接在創(chuàng)建Router時(shí)产上,添加前綴:
const userRouter = new Router({ prefix: '/users' });
userRouter.get('/', (ctx, next) => {
ctx.response.body = "user list~";
});
userRouter.post('/', (ctx, next) => {
ctx.response.body = "create user info~";
});
module.exports = userRouter;
1.4. 請(qǐng)求解析
常見的客戶端傳遞到服務(wù)器參數(shù)的5種方法:
- 方式一:通過get請(qǐng)求中的URL的params棵磷;
- 方式二:通過get請(qǐng)求中的URL的query;
- 方式三:通過post請(qǐng)求中的body的json格式晋涣;
- 方式四:通過post請(qǐng)求中的body的x-www-form-urlencoded格式仪媒;
- 方式五:通過post請(qǐng)求中的form-data格式;
方式一:params
請(qǐng)求地址:http://localhost:8000/users/123
獲取params:
const userRouter = new Router({prefix: "/users"})
userRouter.get("/:id", (ctx, next) => {
console.log(ctx.params.id);
ctx.body = "Hello World";
})
方式二:query
請(qǐng)求地址:http://localhost:8000/login?username=why&password=123
獲取query:
app.use((ctx, next) => {
console.log(ctx.request.query);
ctx.body = "Hello World";
})
方式三:json
請(qǐng)求地址:http://localhost:8000/login
body是json格式:
{
"username": "Tom",
"password": "123456"
}
獲取json數(shù)據(jù):
- 安裝依賴:npm install koa-bodyparser;
- 使用 koa-bodyparser的中間件谢鹊;
app.use(bodyParser());
方式四:x-www-form-urlencoded
請(qǐng)求地址:http://localhost:8000/login
body是x-www-form-urlencoded格式:
獲取json數(shù)據(jù):(和json是一致的)
- 安裝依賴:npm install koa-bodyparser;
- 使用 koa-bodyparser的中間件算吩;
app.use(bodyParser());
方式五:form-data
請(qǐng)求地址:http://localhost:8000/login
body是form-data格式:
解析body中的數(shù)據(jù),我們需要使用multer
- 安裝依賴:npm install koa-multer;
- 使用 multer中間件佃扼;
const upload = multer({});
app.use(upload.any());
app.use((ctx, next) => {
console.log(ctx.req.body);
ctx.body = "Hello World";
});
multer還可以實(shí)現(xiàn)文件的上傳:
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, "./uploads/")
},
filename: (req, file, cb) => {
cb(null, Date.now() + path.extname(file.originalname))
}
})
const upload = multer({
storage
});
const fileRouter = new Router();
fileRouter.post("/upload", upload.single('avatar'), (ctx, next) => {
console.log(ctx.req.file);
})
app.use(fileRouter.routes());
1.5. 響應(yīng)方式
輸出結(jié)果:body
將響應(yīng)主體設(shè)置為以下之一:
- string :字符串?dāng)?shù)據(jù)
- Buffer :Buffer數(shù)據(jù)
- Stream :流數(shù)據(jù)
- Object|| Array:對(duì)象或者數(shù)組
- null :不輸出任何內(nèi)容
如果response.status尚未設(shè)置偎巢,Koa會(huì)自動(dòng)將狀態(tài)設(shè)置為200或204。
比較常見的輸出方式:
ctx.response.body = "Hello World";
ctx.body = {
name: "Tom",
age: 18,
height: 1.88
};
ctx.body = ["abc", "cba", "nba"];
疑惑:ctx.response.body和ctx.body之間的區(qū)別:
- 事實(shí)上松嘶,我們?cè)L問ctx.body時(shí)艘狭,本質(zhì)上是訪問ctx.response.body;
請(qǐng)求狀態(tài):status
請(qǐng)求狀態(tài)我們可以直接給ctx設(shè)置翠订,或者給ctx.response設(shè)置也是一樣的效果:
ctx.status = 201;
ctx.response.status = 204;
1.6. 錯(cuò)誤處理
const Koa = require('koa');
const app = new Koa();
app.use((ctx, next) => {
ctx.app.emit('error', new Error("出錯(cuò)啦"), ctx);
})
app.on('error', (err, ctx) => {
console.log(err.message);
ctx.response.body = "出錯(cuò)啦";
})
app.listen(8000, () => {
console.log("錯(cuò)誤處理服務(wù)啟動(dòng)成功~");
})
1.7. 靜態(tài)服務(wù)器
koa并沒有內(nèi)置部署相關(guān)的功能,所以我們需要使用第三方庫(kù):
npm install koa-static
部署的過程類似于express:
const Koa = require('koa');
const static = require('koa-static');
const app = new Koa();
app.use(static('./build'));
app.listen(8000, () => {
console.log("靜態(tài)服務(wù)器啟動(dòng)成功~");
});
2遵倦、koa 與 express的比較
在學(xué)習(xí)了兩個(gè)框架之后尽超,我們應(yīng)該已經(jīng)可以發(fā)現(xiàn)koa和express的區(qū)別:
從架構(gòu)設(shè)計(jì)上來(lái)說(shuō):
- express是完整和強(qiáng)大的,其中幫助我們內(nèi)置了非常多好用的功能梧躺;
- koa是簡(jiǎn)潔和自由的似谁,它只包含最新的功能傲绣,并不會(huì)對(duì)我們使用其他中間件進(jìn)行任何的限制。
- 甚至是在app中連最基本的get巩踏、post都沒有給我們提供秃诵;
- 我們需要通過自己或者路由來(lái)判斷請(qǐng)求方式或者其他功能;
因?yàn)閑xpress和koa框架他們的核心其實(shí)都是中間件:
但是事實(shí)上它們的中間件的執(zhí)行機(jī)制是不同的塞琼,特別是針對(duì)某個(gè)中間件中包含異步操作時(shí)菠净;
所以,接下來(lái)彪杉,我們?cè)賮?lái)研究一下express和koa中間件的執(zhí)行順序問題毅往;
當(dāng)中間件中沒有異步操作時(shí),express中間件 與 koa中間件 的執(zhí)行順序其實(shí)相同派近,執(zhí)行結(jié)果也相同
但是當(dāng)中間件中包含一步操作時(shí)express 中間件實(shí)現(xiàn)是基于 Callback 回調(diào)函數(shù)同步的攀唯,它不會(huì)去等待異步(Promise)完成
Koa 使用的是一個(gè)洋蔥模型,它的一個(gè)特點(diǎn)是級(jí)聯(lián)渴丸,通過 await next() 控制調(diào)用 “下游” 中間件侯嘀,直到 “下游” 沒有中間件且堆棧執(zhí)行完畢,最終在流回 “上游” 中間件谱轨。
同步執(zhí)行
const express = require('express');
const app = express();
const middleware1 = (req, res, next) => {
req.message = "middleware1";
next();
res.end(req.message);
}
const middleware2 = (req, res, next) => {
req.message = req.message + 'middleware2';
next();
}
const middleware3 = (req, res, next) => {
req.message = req.message + 'middleware3';
}
app.use(middleware1, middleware2, middleware3);
app.listen(8000, () => {
console.log("啟動(dòng)成功~");
})
// middleware1middleware2middleware3
const Koa = require('koa');
const app = new Koa();
const middleware1 = (ctx, next) => {
ctx.message = "middleware1";
next();
ctx.body = ctx.message;
}
const middleware2 = (ctx, next) => {
ctx.message = ctx.message + 'middleware2';
next();
}
const middleware3 = (ctx, next) => {
ctx.message = ctx.message + 'middleware3';
}
app.use(middleware1);
app.use(middleware2);
app.use(middleware3);
app.listen(8001, () => {
console.log("啟動(dòng)成功~");
})
// middleware1middleware2middleware3
- 異步操作
const express = require('express');
const axios = require('axios');
const app = express();
const middleware1 = async (req, res, next) => {
req.message = "middleware1";
await next();
res.end(req.message);
}
const middleware2 = async (req, res, next) => {
req.message = req.message + 'middleware2';
await next();
}
const middleware3 = async (req, res, next) => {
const result = await axios.get('http://123.207.32.32:9001/lyric?id=167876');
req.message = req.message + result.data.lrc.lyric;
console.log(req.message);
}
app.use(middleware1, middleware2, middleware3);
app.listen(8000, () => {
console.log("啟動(dòng)成功~");
})
// middleware1middleware2
// express 中 next()本身是一個(gè)同步函數(shù) 不等待異步請(qǐng)求的返回
// express 底層是不支持Async/Await 的
const Koa = require('koa');
const axios = require('axios');
const app = new Koa();
const middleware1 = async (ctx, next) => {
ctx.message = "middleware1";
await next();
ctx.body = ctx.message;
}
const middleware2 = async (ctx, next) => {
ctx.message = ctx.message + 'middleware2';
await next();
}
const middleware3 = async (ctx, next) => {
const result = await axios.get('http://123.207.32.32:9001/lyric?id=167876');
console.log(result)
ctx.message = ctx.message + result.data.lrc.lyric;
}
app.use(middleware1);
app.use(middleware2);
app.use(middleware3);
app.listen(8001, () => {
console.log("啟動(dòng)成功~");
})
// Koa2 底層已經(jīng)原生支持Async/Await