前言
Koa是基于Node.js的下一代web框架悼沈,由Express團隊打造,特點:優(yōu)雅姐扮、簡潔絮供、靈活、體積小茶敏。幾乎所有功能都需要通過中間件實現(xiàn)壤靶。
準(zhǔn)備
- 檢查Node版本,至少在7.6.0以上惊搏,因為Koa采用很多Es7的語法贮乳,比如async/await,
- 創(chuàng)建項目恬惯、安裝依賴
mkdir study_koa
cd study_koa
npm init -y
npm install koa
一向拆、基本用法
- 創(chuàng)建一個應(yīng)用程序 新建app.js
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
// ctx.body 即服務(wù)端響應(yīng)的數(shù)據(jù)
ctx.body = 'Hello Koa';
})
// 監(jiān)聽端口、啟動程序
app.listen(3000, err => {
if (err) throw err;
console.log('runing...');
})
- 啟動
node app.js
- 訪問 127.0.0.1:3000 會看到頁面顯示Hello Koa
即便沒有給ctx.body 設(shè)置響應(yīng)數(shù)據(jù)酪耳,或訪問不存在的路由浓恳,頁面也會顯示Not Found,這是koa底層做了處理,不像原生Node或Express一樣頁面會一直處于響應(yīng)狀態(tài)颈将。
二梢夯、Context
Koa將Node的request 和 response對象都封裝到了context中,每次請求都會創(chuàng)建一個ctx晴圾,并且在中間件中作為接收器使用颂砸。
以下是剛才訪問時的ctx對象
let ctx = {
// 請求
request: {
method: 'GET',
url: '/',
// request header
header: {
host: '127.0.0.1:3030',
connection: 'keep-alive',
'cache-control': 'max-age=0',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36',
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
cookie: 'connect.sid=s%3AnQtQApNcQ55RmpjnkmQvWNTrdjYhZnlh.1FQUbVqpwpdRj8N6wjv8nOarf8hyzIpcxXN2LPYXGy0'
}
},
// 響應(yīng)
response: {
status: 200,
message: 'ok',
header: {
'content-type': 'text/plain; charset=utf-8',
'content-length': '9'
}
},
app: {
subdomainOffset: 2,
proxy: false,
env: 'development'
},
originalUrl: '/',
// 原生Node的request對象
req: '<original node req>',
// 原生Node的reponse對象
res: '<original node res>',
socket: '<original node socket>'
}
剖析ctx
- 區(qū)分request、response死姚、req沾凄、res
- requset ctx的請求對象
- response ctx的響應(yīng)對象
- req Node的請求對象
- res Node的響應(yīng)對象
注意:繞過Koa的response是不被處理的,避免使用Node的屬性和方法知允,比如:
res.statusCode
res.writeHead()
res.write()
res.end()
即便使用ctx.res.write()也不會得到預(yù)期結(jié)果,比如:ctx.res.write('hello')叙谨,結(jié)果是hellook温鸽,會把message的值拼接上。
- ctx.state
推薦的命名空間手负,用于通過中間件傳遞信息到你的前端視圖涤垫。比如每個頁面都要用到用戶信息,那就可以掛載在ctx.state竟终。類似于添加全局屬性蝠猬。
ctx.state.userInfo = {
name: 'Jack',
age: 18
}
- ctx.app
應(yīng)用程序?qū)嵗?/li>
有關(guān)cookie和session單獨介紹用法。
三统捶、路由
Koa中的路由和Express不同榆芦,Express是把路由集成在Express中,Koa則需要通過kao-router模塊使用喘鸟。
- 安裝:
npm i koa-router
- 使用
const Koa = require('koa');
// 直接調(diào)用的方式
const router = require('koa-router')();
// 或 單獨創(chuàng)建router的實例
const Router = require('koa-router');
const router = new Router();
router.get('/', async ctx => {
ctx.body = 'Hello Router';
})
// 啟動路由
app.use(router.routes()).use(router.allowedMethods())
// 以上為官方推薦方式匆绣,allowedMethods用在routes之后,作用是根據(jù)ctx.status設(shè)置response header.
app.listen(3000, err => {
if (err) throw err;
console.log('runing...');
});
四什黑、中間件
Koa最大的特色和最優(yōu)的設(shè)計就是中間件崎淳,就是在匹配路由之前和匹配路由之后執(zhí)行函數(shù)。
使用app.use()加載中間件愕把。每個中間件接收兩個參數(shù)拣凹,ctx對象和next函數(shù),通過調(diào)用next將執(zhí)行權(quán)交給下一個中間件恨豁。
中間件分為:
- 應(yīng)用級中間件
- 路由級中間件
- 錯誤處理中間件
- 第三方中間件
- 應(yīng)用級中間件
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
// 應(yīng)用級中間件
app.use(async (ctx, next) => {
await next();
})
router.get('/', async ctx => {
ctx.body = 'hello koa';
})
// 啟動路由
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000, err => {
if (err) throw err;
console.log('runing...')
})
任何路由都會先經(jīng)過應(yīng)用級中間件嚣镜,當(dāng)執(zhí)行完成next后再去匹配相應(yīng)的路由。
- 路由中間件
router.get('/user', async (ctx, next) => {
console.log(111)
await next();
})
router.get('/user', async (ctx, next) => {
console.log(222)
await next();
})
router.get('/user', async ctx => {
console.log(333)
ctx.body = 'Hello'
})
// 依次打印
111
222
333
路由匹配過程中圣絮,對于相同路由會從上往下依次執(zhí)行中間件淑履,直到最后一個沒有next參數(shù)的中間件為止。
- 錯誤處理中間件
app.use(async (ctx, next)=> {
await next();
if(ctx.status === 404){
ctx.body="404頁面"
}
});
路由在匹配成功并執(zhí)行完相應(yīng)的操作后還會再次進(jìn)入應(yīng)用級中間件執(zhí)行 next 之后的邏輯撵枢。所以對于404、500等錯誤可以在最外層的(第一個)應(yīng)用級中間件的next之后做相應(yīng)的處理凡涩。
如果只有一個應(yīng)用級中間件的話,順序就無所謂所有路由中間件之前和之后了疹蛉。
第三方中間件
類似于koa-router活箕、koa-bodyparser等就是第三方中間件。中間件的合成
koa-compose
模塊可以將多個中間件合成為一個可款。
const compose = require('koa-compose')
const first = asycn (ctx, next) => {
await next();
}
const second = async ctx => {
ctx.body = 'Hello';
}
const middle = compose([first, second]);
app.use(middle);
- 中間件的執(zhí)行順序
多個中間件會形成堆棧結(jié)構(gòu)育韩,按先進(jìn)后出順序執(zhí)行
app.use(async (ctx, next) => {
console.log('1中間件第1次執(zhí)行')
await next();
console.log('7中間件第7次執(zhí)行')
})
app.use(async (ctx, next) => {
console.log('2中間件第2次執(zhí)行');
await next();
console.log('6中間件第6次執(zhí)行')
})
router.get('/user', async (ctx, next) => {
console.log('3中間件第3次執(zhí)行')
await next()
console.log('5中間件第5次執(zhí)行')
})
router.get('/user', (ctx, next) => {
console.log('4中間件第4次執(zhí)行')
ctx.body = 'Hello Koa';
})
// 1中間件第1次執(zhí)行
// 2中間件第2次執(zhí)行
// 3中間件第3次執(zhí)行
// 4中間件第4次執(zhí)行
// 5中間件第5次執(zhí)行
// 6中間件第6次執(zhí)行
// 7中間件第7次執(zhí)行
由此可以看出中間件的執(zhí)行順序是先進(jìn)后出的方式。類似于洋蔥圖闺鲸。
五筋讨、獲取請求數(shù)據(jù)
- GET 傳值
-
Koa 中 GET傳值通過request接收,有兩種方式: query 和 querystring
query:返回的是參數(shù)對象摸恍。 {name: 'jack', age: 12}
querystring:返回的是請求字符串悉罕。 name=jack&age=12 query和querystring可以從request中獲取,也可以直接從ctx中獲取立镶。
let request = ctx.request;
let query = request.query;
let querystring = request.querystring;
// 直接ctx獲取
ctx.query
ctx.querystring
- POST 傳值
通過post傳遞的值我們可以通過原生Node封裝壁袄,也可以通過第三方模塊接收。
- 自定義封裝
const querystring = require('querystring');
module.exports = ctx => {
return new Promise((resolve, reject) => {
try {
let data = '';
// ctx.req實際上就是原生node中的req
ctx.req.on('data', (chunk) => {
data += chunk;
})
ctx.req.on('end', () => {
data = querystring.parse(data);
resolve(data);
})
}
catch(err) {
reject(err);
}
})
}
- 使用koa-bodyparser模塊
const bodyParser = require('koa-bodyparser');
app.use(bodyParser());
// 獲取
ctx.request.body
六媚媒、處理靜態(tài)資源
對于諸如js嗜逻、css、img等靜態(tài)資源采用koa-static中間件處理缭召。
npm i koa-static
配置:
比如靜態(tài)目錄為static:
// 靜態(tài)資源配置
// app.use(require('koa-static')('static'))
// or
// app.use(require('koa-static')('./static'))
// or
// app.use(require('koa-static')(__dirname + '/static'))
// or 使用path.join() 的時候栈顷,static前面的/可加可不加,該方法會內(nèi)部會做處理
app.use(require('koa-static')(path.join(__dirname, 'static')));
在模板中即可訪問:
<link rel="stylesheet" href="/css/header.css">
<img src="/image/account.eb695dc.png"/>
七嵌巷、模板引擎
koa生態(tài)的模板引擎挺多的妨蛹,比如ejs、art-template等晴竞。
1. koa-ejs
npm i koa-ejs
const render = require('koa-ejs');
- 配置
render(app, {
// views 視圖根目錄
root: path.join(__dirname, 'views'),
layout: 'template', // 公用文件 若要禁用蛙卤,設(shè)置為false即可
viewExt: 'html', // 擴展名
cache: false, // 緩存 default true
debug: false // 如果開啟debug模式,則會在終端實時打印信息 default false
});
- 使用
/**
* 參數(shù)1: 模板名
* 參數(shù)2: 數(shù)據(jù)(可選)
*/
await ctx.render(templateName, data);
2. art-template
npm install --save art-template
npm install --save koa-art-template
- 配置
const render = require('koa-art-template');
// 配置模板引擎
render(app, {
root: path.join(__dirname, 'views'), // 視圖目錄
extname: '.html',
debug: process.env.NODE_ENV !== 'production'
});
使用方式和ejs一樣噩死。
性能上相比颤难,art-template比ejs快很多,開發(fā)中用的最多的還是art-template已维。
八行嗤、cookie 和 session
http是無狀態(tài)、無連接的垛耳。不會對之前發(fā)生過的請求和相應(yīng)狀態(tài)進(jìn)行管理栅屏。也就是說飘千,無法根據(jù)之前的狀態(tài)進(jìn)行本次的請求處理。
比如訪問淘寶首頁并登錄賬號后栈雳,當(dāng)再打開淘寶其他頁面時护奈,因為每一次的訪問都是獨立的,服務(wù)器并不知道你已經(jīng)登錄哥纫,所以還是不能下單或者加購物車之類的操作霉旗。
cookie
- cookie簡單介紹
cookie是客戶端第一次訪問服務(wù)器的時候,服務(wù)器在下行HTTP報文時通過響應(yīng)頭的set-cookie
字段給瀏覽器分配的一個具有特殊標(biāo)識的文本信息蛀骇,此后當(dāng)客戶端再次訪問同一域名時厌秒,便會將該字段通過請求頭攜帶到服務(wù)器。注意: 第一次訪問服務(wù)器是不可能攜帶cookie的擅憔。
缺陷: 1鸵闪、cookie的數(shù)據(jù)存放在客戶端,不安全暑诸,容易被(CSRF)跨站請求偽造岛马。攻擊者可以借助受害者的 Cookie 騙取服務(wù)器的信任,可以在受害者毫不知情的情況下以受害者名義偽造請求發(fā)送給受攻擊服務(wù)器屠列,從而在并未授權(quán)的情況下執(zhí)行在權(quán)限保護之下的操作。2伞矩、單個cookie保存的數(shù)據(jù)不能超過4K笛洛,很多瀏覽器都限制一個站點最多保存20個cookie。
- Koa中使用cookie
- 設(shè)置cookie
ctx.cookies.get(name, [options])
通過 options 獲取 cookie name:
signed 所請求的cookie應(yīng)該被簽名
- 設(shè)置cookie
ctx.cookies.set(name, value, [options])
通過 options 設(shè)置 cookie name 的 value:
- maxAge: 有效期(一個數(shù)字表示從 Date.now() 得到的毫秒數(shù))
- signed: cookie 簽名值
- expires: cookie 過期的 Date
- path: cookie作用域(指定了主機下的哪些路徑可以接受Cookie), 默認(rèn)是
/
- domain: cookie作用域 (指定了哪些主機可以接受Cookie乃坤。如果不指定苛让,默認(rèn)為當(dāng)前文檔的主機<不包含子域名>;如果指定了
Domain
,則一般包含子域名湿诊。
) - secure: 安全 cookie狱杰。即標(biāo)記為 Secure 的Cookie只可以通過被HTTPS協(xié)議加密過的請求發(fā)送給服務(wù)端。
- httpOnly: 只可讓服務(wù)器通過http請求訪問cookie,不可通過js的
document.cookie
API訪問厅须,可以避免XSS攻擊仿畸。 默認(rèn)是 true。 - SameSite: 允許服務(wù)器要求某個cookie在跨站請求時不會被發(fā)送朗和,從而可以阻止跨站請求偽造攻擊(CSRF)错沽。可選值:
- None:瀏覽器會在同站請求眶拉、跨站請求下繼續(xù)發(fā)送cookies千埃,不區(qū)分大小寫。
- Strict: 瀏覽器將只在訪問相同站點時發(fā)送cookie忆植。
- Lax: 在新版本瀏覽器中放可,為默認(rèn)選項谒臼。大多數(shù)情況不發(fā)送第三方 Cookie,只有當(dāng)用戶從外部站點導(dǎo)航到URL時才會發(fā)送耀里,包括鏈接(a標(biāo)簽)蜈缤、預(yù)加載(link)、get表單备韧。
- overwrite: 一個布爾值劫樟,表示是否覆蓋以前設(shè)置的同名的 cookie (默認(rèn)是 false). 如果是 true, 在同一個請求中設(shè)置相同名稱的所有 Cookie(不管路徑或域)是否在設(shè)置此Cookie 時從 Set-Cookie 標(biāo)頭中過濾掉。
設(shè)置中文cookie
通過buffer轉(zhuǎn)成base64存進(jìn)去织堂,取出來是再轉(zhuǎn)回中文叠艳。
// 轉(zhuǎn)base64
Buffer.from('張三').toString('base64');
// 轉(zhuǎn)回中文
Buffer.from(buf, 'base64').toString();
session
- seeion簡單介紹
session是另一種記錄客戶狀態(tài)的機制,不同的是cookie保存在客戶端瀏覽器中易阳,而session保存在服務(wù)器上附较。
前面說過,cookie 是存放在客戶端潦俺,不是很安全拒课,用戶可以自己手動把cookie種在客戶端以欺騙服務(wù)器。而session是存儲在服務(wù)端的事示,所以對于較重要的數(shù)據(jù)存儲在session早像。
缺點: session會在一定時間內(nèi)保存在服務(wù)器上。當(dāng)訪問增多肖爵,會比較占用你服務(wù)器的性能卢鹦。
- session 的工作機制
當(dāng)瀏覽器第一次請求服務(wù)器時,服務(wù)器端會創(chuàng)建一個session對象劝堪,生成一個類似于key-value的鍵值對冀自, 然后將key(cookie)下發(fā)到客戶端,當(dāng)客戶端再訪問時秒啦,攜帶key(cookie)熬粗,找到對應(yīng)的session(value)。 生產(chǎn)中用戶的信息都保存在session中余境。
- koa-session 的使用
npm install koa-session
const Koa = require('koa');
const session = require('koa-session');
const app = new Koa();
app.keys = ['some secret hurr'];
const CONFIG = {
key: 'koa:sess', // 返給瀏覽器 cookie 的key 默認(rèn)是 'kao:sess'
maxAge: 86400000, // cookie的過期時間 maxAge in ms (default is 1 days)
autoCommit: true, // (boolean) 自動給客戶端下發(fā)cookie 并設(shè)置session
overwrite: true, // 是否可以覆蓋之前同名的cookie (默認(rèn)default true)
httpOnly: true, // cookie是否只有服務(wù)器端可以訪問 httpOnly or not (default true)
signed: true, // 簽名默認(rèn)true
rolling: false, // 在每次響應(yīng)時強制設(shè)置session標(biāo)識符cookie驻呐,到期時被重置設(shè)置過期倒計時。(默認(rèn)為false)
renew: false, // 當(dāng)session快過期時更新session芳来,這樣就可以始終保持用戶登錄 默認(rèn)是false
};
以上配置選項常用的就是key暴氏、maxAge、httpOver绣张。
renew應(yīng)用:比如我們登錄賬號寫一篇博客答渔,寫了一半cookie過期了,當(dāng)我們提交的時候就會退出登錄侥涵,體驗很不好沼撕,而且寫好的博客丟失宋雏。
九、重定向
301和302重定向狀態(tài)碼區(qū)別务豺。302為臨時重定向磨总,301永久重定向。Koa中默認(rèn)為302笼沥。詳細(xì)信息查看這篇博客 301和302重定向介紹
字符串 “back” 是特別提供Referrer支持的蚪燕,當(dāng)Referrer不存在時,使用 alt 或“/”奔浅。
ctx.redirect('back');
ctx.redirect('back', '/login');
ctx.redirect('/login');
要更改 “302” 的默認(rèn)狀態(tài)馆纳,只需在該調(diào)用之前或之后分配狀態(tài)。要變更主體請在此調(diào)用之后:
ctx.status = 301;
ctx.redirect('/cart');
ctx.body = 'Redirecting to shopping cart';
十汹桦、跨域請求
解決跨域的方式有很多種鲁驶,個人認(rèn)為最好的方案是在服務(wù)器端設(shè)置支持跨域。
原生設(shè)置方式:
下面詳細(xì)說明在koa2中設(shè)置具體的請求頭信息:
app.use(async (ctx, next) => {
// 允許所有域名請求
ctx.set("Access-Control-Allow-Origin", "*")
// 只允許 http://localhost:8080 域名的請求
// ctx.set("Access-Control-Allow-Origin", "http://localhost:8080");
// 設(shè)置允許的跨域請求方式
ctx.set("Access-Control-Allow-Methods", "OPTIONS, GET, PUT, POST, DELETE")
// 字段是必需的舞骆。值一個逗號分隔的字符串钥弯,表示服務(wù)器所支持的所有頭信息字段.
ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type")
// 服務(wù)器收到請求以后,檢查了Origin督禽、Access-Control-Request-Method和Access-Control-Request-Headers字段以后脆霎,確認(rèn)允許跨源請求,即可做出響應(yīng)狈惫。
// Content-Type表示具體請求中的媒體類型信息
ctx.set("Content-Type", "application/json;charset=utf-8")
// 該字段可選睛蛛。它的值是一個布爾值,表示是否允許發(fā)送Cookie虱岂。默認(rèn)情況下,Cookie不包括在CORS請求之中菠红。 當(dāng)設(shè)置成允許請求攜帶憑證cookie時第岖,需要保證"Access-Control-Allow-Origin"是服務(wù)器有的域名,而不能是"*";
ctx.set("Access-Control-Allow-Credentials", true);
// 該字段可選试溯,用來指定本次預(yù)檢請求的有效期蔑滓,單位為秒。
// 當(dāng)請求方法是PUT或DELETE等特殊方法或者Content-Type字段的類型是application/json時遇绞,服務(wù)器會提前發(fā)送一次請求進(jìn)行驗證
// 下面的的設(shè)置只本次驗證的有效時間键袱,即在該時間段內(nèi)服務(wù)端可以不用進(jìn)行驗證
ctx.set("Access-Control-Max-Age", 300);
/*
CORS請求時,XMLHttpRequest對象的getResponseHeader()方法只能拿到6個基本字段:
Cache-Control摹闽、
Content-Language蹄咖、
Content-Type、
Expires付鹿、
Last-Modified澜汤、
Pragma蚜迅。
*/
// 需要獲取其他字段時,使用Access-Control-Expose-Headers俊抵,
// getResponseHeader('myData')可以返回我們所需的值
ctx.set("Access-Control-Expose-Headers", "myData")
await next()
})
使用中間件方式
在koa2中谁不,解決跨域請求還可使用中間件koa2-cors
- 安裝:
npm i koa2-cors -S
- 注冊中間件
const cors = require('koa2-cors')
app.use(cors())
十一、node發(fā)送郵件
node 發(fā)送郵件可以使用nodemailer
三方模塊徽诲。
安裝:
npm i nodemailer
const nodemailer = require("nodemailer")
async function main() {
let transporter = nodemailer.createTransport({
host: "smtp.qq.com",
port: 465,
secure: true, // true for 465, false for other ports
auth: {
user: 'test@qq.com',
pass: 'akphfubplzqdbdfh'
}
})
let info = await transporter.sendMail({
from: 'test@qq.com', // sender address
to: "bar@qq.com", // 接收地址 多個郵箱使用 ','分割
subject: "Hello 老胖", // 郵件主題
text: "胖子蹲啊胖子蹲胖子蹲完瘦子蹲", // plain text body
// html: "<b>嗯好</b>" // html body
})
console.log("Message sent: %s", info.messageId)
console.log("Preview URL: %s", nodemailer.getTestMessageUrl(info))
}
main().catch(console.error)
使用說明:
創(chuàng)建transporter配置信息
在node_modules>nodemailer>lib>well_known>services.json
中查看對應(yīng)的配置信息刹帕。配置發(fā)件郵箱相應(yīng)的host、port及secure谎替。-
獲取發(fā)件箱密碼(類似于授權(quán)偷溺,獲取key),以qq郵箱為例
qq郵箱>設(shè)置>賬戶>POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服務(wù)院喜,點擊開啟獲取pass -
配置user和pass
- user即發(fā)件地址
- pass就是剛才授權(quán)獲取的密碼
-
sendMail配置信息
- from 發(fā)件地址
- to 收件地址 如果多個收件地址亡蓉,用
,
分割即可 - subject 郵件主題
- text 文件內(nèi)容
- html html內(nèi)容
注意:text和html只能配置一個
更多詳細(xì)配置信息及功能參照官網(wǎng)