Koa2 快速入門(mén)

一歪沃、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>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市咐刨,隨后出現(xiàn)的幾起案子昙衅,更是在濱河造成了極大的恐慌,老刑警劉巖定鸟,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件而涉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡联予,警方通過(guò)查閱死者的電腦和手機(jī)啼县,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)沸久,“玉大人季眷,你說(shuō)我怎么就攤上這事【砜瑁” “怎么了子刮?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)窑睁。 經(jīng)常有香客問(wèn)我挺峡,道長(zhǎng),這世上最難降的妖魔是什么担钮? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任橱赠,我火速辦了婚禮,結(jié)果婚禮上箫津,老公的妹妹穿的比我還像新娘狭姨。我一直安慰自己宰啦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布饼拍。 她就那樣靜靜地躺著赡模,像睡著了一般。 火紅的嫁衣襯著肌膚如雪惕耕。 梳的紋絲不亂的頭發(fā)上纺裁,一...
    開(kāi)封第一講書(shū)人閱讀 51,698評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音司澎,去河邊找鬼欺缘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛挤安,可吹牛的內(nèi)容都是我干的谚殊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蛤铜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼嫩絮!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起围肥,我...
    開(kāi)封第一講書(shū)人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤剿干,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后穆刻,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體置尔,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年氢伟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了榜轿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡朵锣,死狀恐怖谬盐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情诚些,我是刑警寧澤飞傀,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站诬烹,受9級(jí)特大地震影響助析,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜椅您,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望寡键。 院中可真熱鬧掀泳,春花似錦雪隧、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至马僻,卻和暖如春庄拇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背韭邓。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工措近, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人女淑。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓瞭郑,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親鸭你。 傳聞我的和親對(duì)象是個(gè)殘疾皇子屈张,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 原文鏈接:http://www.reibang.com/p/6b816c609669 前傳 出于興趣最近開(kāi)始研究k...
    懸筆e絕閱讀 7,217評(píng)論 1 11
  • 框架提出的背景 ES6/7帶來(lái)的變革 自ES6確定和ES7中async/await開(kāi)始普及,Node的發(fā)展變得更加...
    宮若石閱讀 8,510評(píng)論 1 14
  • koa2框架筆記 Node.js是一一個(gè)異步的世界袱巨,官方API支持的都是callback 形式的異步編程模型阁谆,這會(huì)...
    wanminglei閱讀 571評(píng)論 0 0
  • 前傳 出于興趣最近開(kāi)始研究koa2,由于之前有過(guò)一些express經(jīng)驗(yàn)愉老,以為koa還是很好上手的场绿,但是用起來(lái)發(fā)現(xiàn)還...
    阿_希爸閱讀 119,590評(píng)論 81 240
  • Koa 學(xué)習(xí) 歷史 Express Express是第一代最流行的web框架,它對(duì)Node.js的http進(jìn)行了封...
    Junting閱讀 2,826評(píng)論 0 0