一歪沃、koa2 快速開(kāi)始
1腕让、環(huán)境準(zhǔn)備
因?yàn)閚ode.js v7.6.0開(kāi)始完全支持async/await承粤,不需要加flag妆距,所以node.js環(huán)境都要7.6.0以上 node.js環(huán)境 版本v7.6以上 npm 版本3.x以上
2校坑、快速開(kāi)始
2.1 安裝koa2
# 初始化package.json
npm init
# 安裝koa2
npm install koa
2.2 hello world 代碼
const Koa = require('koa')
const app = new Koa()
app.use( async ( ctx ) => {
ctx.body = 'hello koa2'
})
app.listen(3000)
console.log('[demo] start-quick is starting at port 3000')
2.3 啟動(dòng)demo
由于koa2是基于async/await操作中間件轧飞,目前node.js 7.x的harmony模式下才能使用,所以啟動(dòng)的時(shí)的腳本如下:
node index.js
二撒踪、async/await使用
1过咬、快速上手理解
先復(fù)制以下這段代碼,在粘貼在chrome的控制臺(tái)console中制妄,按回車(chē)鍵執(zhí)行
function getSyncTime() {
return new Promise((resolve, reject) => {
try {
let startTime = new Date().getTime()
setTimeout(() => {
let endTime = new Date().getTime()
let data = endTime - startTime
resolve( data )
}, 500)
} catch ( err ) {
reject( err )
}
})
}
async function getSyncData() {
let time = await getSyncTime()
let data = `endTime - startTime = ${time}`
return data
}
async function getData() {
let data = await getSyncData()
console.log( data )
}
getData()
2掸绞、從上述例子可以看出 async/await 的特點(diǎn):
- 可以讓異步邏輯用同步寫(xiě)法實(shí)現(xiàn)
- 最底層的await返回需要是Promise對(duì)象
- 可以通過(guò)多層 async function 的同步寫(xiě)法代替?zhèn)鹘y(tǒng)的callback嵌套
三、koa2簡(jiǎn)析結(jié)構(gòu)
1耕捞、源碼文件
├── lib │ ├── application.js │ ├── context.js │ ├── request.js │ └── response.js └── package.json
這個(gè)就是 GitHub https://github.com/koajs/koa上開(kāi)源的koa2源碼的源文件結(jié)構(gòu)衔掸,核心代碼就是lib目錄下的四個(gè)文件
- application.js 是整個(gè)koa2 的入口文件,封裝了context俺抽,request敞映,response,以及最核心的中間件處理流程磷斧。
- context.js 處理應(yīng)用上下文振愿,里面直接封裝部分request.js和response.js的方法
- request.js 處理http請(qǐng)求
- response.js 處理http響應(yīng)
2、koa2特性
- 只提供封裝好http上下文弛饭、請(qǐng)求冕末、響應(yīng),以及基于async/await的中間件容器侣颂。
- 利用ES7的async/await的來(lái)處理傳統(tǒng)回調(diào)嵌套問(wèn)題和代替koa@1的generator档桃,但是需要在node.js 7.x的harmony模式下才能支持async/await。
- 中間件只支持 async/await 封裝的憔晒,如果要使用koa@1基于generator中間件藻肄,需要通過(guò)中間件koa-convert封裝一下才能使用。
四拒担、koa中間件開(kāi)發(fā)和使用
- koa v1和v2中使用到的中間件的開(kāi)發(fā)和使用
- generator 中間件開(kāi)發(fā)在koa v1和v2中使用
- async await 中間件開(kāi)發(fā)和只能在koa v2中使用
1嘹屯、generator中間件開(kāi)發(fā)
1.1 generator中間件開(kāi)發(fā)
generator中間件返回的應(yīng)該是function * () 函數(shù)
/* ./middleware/logger-generator.js */
function log( ctx ) {
console.log( ctx.method, ctx.header.host + ctx.url )
}
module.exports = function () {
return function * ( next ) {
// 執(zhí)行中間件的操作
log( this )
if ( next ) {
yield next
}
}
}
1.2 generator中間件在koa@1中的使用
generator 中間件在koa v1中可以直接use使用
const koa = require('koa') // koa v1
const loggerGenerator = require('./middleware/logger-generator')
const app = koa()
app.use(loggerGenerator())
app.use(function *( ) {
this.body = 'hello world!'
})
app.listen(3000)
console.log('the server is starting at port 3000')
1.3 generator中間件在koa@2中的使用
generator 中間件在koa v2中需要用koa-convert封裝一下才能使用
const Koa = require('koa') // koa v2
const convert = require('koa-convert')
const loggerGenerator = require('./middleware/logger-generator')
const app = new Koa()
app.use(convert(loggerGenerator()))
app.use(( ctx ) => {
ctx.body = 'hello world!'
})
app.listen(3000)
console.log('the server is starting at port 3000')
2、async中間件開(kāi)發(fā)
2.1 async 中間件開(kāi)發(fā)
/* ./middleware/logger-async.js */
function log( ctx ) {
console.log( ctx.method, ctx.header.host + ctx.url )
}
module.exports = function () {
return async function ( ctx, next ) {
log(ctx);
await next()
}
}
2.2 async 中間件在koa@2中使用
async 中間件只能在 koa v2中使用
const Koa = require('koa') // koa v2
const loggerAsync = require('./middleware/logger-async')
const app = new Koa()
app.use(loggerAsync())
app.use(( ctx ) => {
ctx.body = 'hello world!'
})
app.listen(3000)
console.log('the server is starting at port 3000')
Ⅱ澎蛛、路由
一抚垄、koa2 原生路由實(shí)現(xiàn)
1、簡(jiǎn)單例子
const Koa = require('koa')
const app = new Koa()
app.use( async ( ctx ) => {
let url = ctx.request.url
ctx.body = url
})
app.listen(3000)
訪問(wèn) http://localhost:3000/hello/world 頁(yè)面會(huì)輸出 /hello/world,也就是說(shuō)上下文的請(qǐng)求request對(duì)象中url之就是當(dāng)前訪問(wèn)的路徑名稱(chēng)呆馁,可以根據(jù)ctx.request.url 通過(guò)一定的判斷或者正則匹配就可以定制出所需要的路由桐经。
2、定制化的路由
demo源碼
https://github.com/ChenShenhai/koa2-note/tree/master/demo/route-simple
2.1 源碼文件目錄
.
├── index.js
├── package.json
└── view
├── 404.html
├── index.html
└── todo.html
2.2 demo源碼
const Koa = require('koa')
const fs = require('fs')
const app = new Koa()
/**
* 用Promise封裝異步讀取文件方法
* @param {string} page html文件名稱(chēng)
* @return {promise}
*/
function render( page ) {
return new Promise(( resolve, reject ) => {
let viewUrl = `./view/${page}`
fs.readFile(viewUrl, "binary", ( err, data ) => {
if ( err ) {
reject( err )
} else {
resolve( data )
}
})
})
}
/**
* 根據(jù)URL獲取HTML內(nèi)容
* @param {string} url koa2上下文的url浙滤,ctx.url
* @return {string} 獲取HTML文件內(nèi)容
*/
async function route( url ) {
let view = '404.html'
switch ( url ) {
case '/':
view = 'index.html'
break
case '/index':
view = 'index.html'
break
case '/todo':
view = 'todo.html'
break
case '/404':
view = '404.html'
break
default:
break
}
let html = await render( view )
return html
}
app.use( async ( ctx ) => {
let url = ctx.request.url
let html = await route( url )
ctx.body = html
})
app.listen(3000)
console.log('[demo] route-simple is starting at port 3000')
2.3 運(yùn)行demo
執(zhí)行運(yùn)行腳本
node -harmony index.js
二阴挣、koa-router中間件
如果依靠ctx.request.url去手動(dòng)處理路由,將會(huì)寫(xiě)很多處理代碼纺腊,這時(shí)候就需要對(duì)應(yīng)的路由的中間件對(duì)路由進(jìn)行控制畔咧,這里介紹一個(gè)比較好用的路由中間件koa-router
1、安裝koa-router中間件
# koa2 對(duì)應(yīng)的版本是 7.x
npm install --save koa-router@7
2揖膜、快速使用koa-router
const Koa = require('koa')
const fs = require('fs')
const app = new Koa()
const Router = require('koa-router')
let home = new Router()
// 子路由1
home.get('/', async ( ctx )=>{
let html = `
<ul>
<li><a href="/page/helloworld">/page/helloworld</a></li>
<li><a href="/page/404">/page/404</a></li>
</ul>
`
ctx.body = html
})
// 子路由2
let page = new Router()
page.get('/404', async ( ctx )=>{
ctx.body = '404 page!'
}).get('/helloworld', async ( ctx )=>{
ctx.body = 'helloworld page!'
})
// 裝載所有子路由
let router = new Router()
router.use('/', home.routes(), home.allowedMethods())
router.use('/page', page.routes(), page.allowedMethods())
// 加載路由中間件
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000, () => {
console.log('[demo] route-use-middleware is starting at port 3000')
})
Ⅲ誓沸、請(qǐng)求數(shù)據(jù)獲取
一、GET請(qǐng)求數(shù)據(jù)獲取
1壹粟、使用方法
在koa中拜隧,獲取GET請(qǐng)求數(shù)據(jù)源頭是koa中request對(duì)象中的query方法或querystring方法,query返回是格式化好的參數(shù)對(duì)象趁仙,querystring返回的是請(qǐng)求字符串洪添,由于ctx對(duì)request的API有直接引用的方式,所以獲取GET請(qǐng)求數(shù)據(jù)有兩個(gè)途徑雀费。
- 是從上下文中直接獲取 請(qǐng)求對(duì)象ctx.query干奢,返回如 { a:1, b:2 } 請(qǐng)求字符串 ctx.querystring,返回如 a=1&b=2
- 是從上下文的request對(duì)象中獲取 請(qǐng)求對(duì)象ctx.request.query盏袄,返回如 { a:1, b:2 } 請(qǐng)求字符串 ctx.request.querystring忿峻,返回如 a=1&b=2
2、舉個(gè)例子
2.1 例子代碼
const Koa = require('koa')
const app = new Koa()
app.use( async ( ctx ) => {
let url = ctx.url
// 從上下文的request對(duì)象中獲取
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 = {
url,
req_query,
req_querystring,
ctx_query,
ctx_querystring
}
})
app.listen(3000, () => {
console.log('[demo] request get is starting at port 3000')
})
2.2 執(zhí)行程序
node get.js
二貌矿、POST請(qǐng)求參數(shù)獲取
1炭菌、原理
對(duì)于POST請(qǐng)求的處理,koa2沒(méi)有封裝獲取參數(shù)的方法逛漫,需要通過(guò)解析上下文context中的原生node.js請(qǐng)求對(duì)象req,將POST表單數(shù)據(jù)解析成query string(例如:a=1&b=2&c=3)赘艳,再將query string 解析成JSON格式(例如:{"a":"1", "b":"2", "c":"3"})
注意:ctx.request是context經(jīng)過(guò)封裝的請(qǐng)求對(duì)象酌毡,ctx.req是context提供的node.js原生HTTP請(qǐng)求對(duì)象,同理ctx.response是context經(jīng)過(guò)封裝的響應(yīng)對(duì)象蕾管,ctx.res是context提供的node.js原生HTTP請(qǐng)求對(duì)象枷踏。
解析出POST請(qǐng)求上下文中的表單數(shù)據(jù)
// 解析上下文里node原生請(qǐng)求的POST參數(shù)
function parsePostData( ctx ) {
return new Promise((resolve, reject) => {
try {
let postdata = "";
ctx.req.addListener('data', (data) => {
postdata += data
})
ctx.req.addListener("end",function(){
let parseData = parseQueryStr( postdata )
resolve( parseData )
})
} catch ( err ) {
reject(err)
}
})
}
// 將POST請(qǐng)求參數(shù)字符串解析成JSON
function parseQueryStr( queryStr ) {
let queryData = {}
let queryStrList = queryStr.split('&')
console.log( queryStrList )
for ( let [ index, queryStr ] of queryStrList.entries() ) {
let itemList = queryStr.split('=')
queryData[ itemList[0] ] = decodeURIComponent(itemList[1])
}
return queryData
}
2、舉個(gè)例子
2.1 例子代碼
const Koa = require('koa')
const app = new Koa()
app.use( async ( ctx ) => {
if ( ctx.url === '/' && ctx.method === 'GET' ) {
// 當(dāng)GET請(qǐng)求時(shí)候返回表單頁(yè)面
let html = `
<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>
`
ctx.body = html
} else if ( ctx.url === '/' && ctx.method === 'POST' ) {
// 當(dāng)POST請(qǐng)求的時(shí)候掰曾,解析POST表單里的數(shù)據(jù)旭蠕,并顯示出來(lái)
let postData = await parsePostData( ctx )
ctx.body = postData
} else {
// 其他請(qǐng)求顯示404
ctx.body = '<h1>404!!掏熬! o(╯□╰)o</h1>'
}
})
// 解析上下文里node原生請(qǐng)求的POST參數(shù)
function parsePostData( ctx ) {
return new Promise((resolve, reject) => {
try {
let postdata = "";
ctx.req.addListener('data', (data) => {
postdata += data
})
ctx.req.addListener("end",function(){
let parseData = parseQueryStr( postdata )
resolve( parseData )
})
} catch ( err ) {
reject(err)
}
})
}
// 將POST請(qǐng)求參數(shù)字符串解析成JSON
function parseQueryStr( queryStr ) {
let queryData = {}
let queryStrList = queryStr.split('&')
console.log( queryStrList )
for ( let [ index, queryStr ] of queryStrList.entries() ) {
let itemList = queryStr.split('=')
queryData[ itemList[0] ] = decodeURIComponent(itemList[1])
}
return queryData
}
app.listen(3000, () => {
console.log('[demo] request post is starting at port 3000')
})
2.2 啟動(dòng)例子
node post.js
三佑稠、koa-bodyparser中間件
1、原理
對(duì)于POST請(qǐng)求的處理旗芬,koa-bodyparser中間件可以把koa2上下文的formData數(shù)據(jù)解析到ctx.request.body中
安裝koa2版本的koa-bodyparser@3中間件
npm install --save koa-bodyparser@3
2舌胶、舉個(gè)例子
2.1 例子代碼
const Koa = require('koa')
const app = new Koa()
const bodyParser = require('koa-bodyparser')
// 使用ctx.body解析中間件
app.use(bodyParser())
app.use( async ( ctx ) => {
if ( ctx.url === '/' && ctx.method === 'GET' ) {
// 當(dāng)GET請(qǐng)求時(shí)候返回表單頁(yè)面
let html = `
<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>
`
ctx.body = html
} else if ( ctx.url === '/' && ctx.method === 'POST' ) {
// 當(dāng)POST請(qǐng)求的時(shí)候,中間件koa-bodyparser解析POST表單里的數(shù)據(jù)疮丛,并顯示出來(lái)
let postData = ctx.request.body
ctx.body = postData
} else {
// 其他請(qǐng)求顯示404
ctx.body = '<h1>404aI!誊薄! o(╯□╰)o</h1>'
}
})
app.listen(3000, () => {
console.log('[demo] request post is starting at port 3000')
})
2.2 啟動(dòng)例子
node post-middleware.js
Ⅳ履恩、靜態(tài)資源加載
koa-static中間件使用
1、使用例子
const Koa = require('koa')
const path = require('path')
const static = require('koa-static')
const app = new Koa()
// 靜態(tài)資源目錄對(duì)于相對(duì)入口文件index.js的路徑
const staticPath = './static'
app.use(static(
path.join( __dirname, staticPath)
))
app.use( async ( ctx ) => {
ctx.body = 'hello world'
})
app.listen(3000, () => {
console.log('[demo] static-use-middleware is starting at port 3000')
})
Ⅴ呢蔫、模板引擎
koa2加載模板引擎
1切心、快速開(kāi)始
1.1 安裝模塊
# 安裝koa模板使用中間件
npm install --save koa-views
# 安裝ejs模板引擎
npm install --save ejs
1.2 使用模板引擎
文件目錄
├── package.json
├── index.js
└── view
└── index.ejs
./index.js文件
const Koa = require('koa')
const views = require('koa-views')
const path = require('path')
const app = new Koa()
// 加載模板引擎
app.use(views(path.join(__dirname, './view'), {
extension: 'ejs'
}))
app.use( async ( ctx ) => {
let title = 'hello koa2'
await ctx.render('index', {
title,
})
})
app.listen(3000)
./view/index.ejs 模板
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<h1><%= title %></h1>
<p>EJS Welcome to <%= title %></p>
</body>
</html>