Koa2

Koa 必須使用 7.6 以上的版本。如果你的版本低于這個(gè)要求扣蜻,就要先升級(jí) Node逆巍。

基本用法

Koa 提供一個(gè) Context 對(duì)象,表示一次對(duì)話的上下文(包括 HTTP 請(qǐng)求和 HTTP 回復(fù))莽使。通過(guò)加工這個(gè)對(duì)象锐极,就可以控制返回給用戶的內(nèi)容。
Context.response.body屬性就是發(fā)送給用戶的內(nèi)容芳肌。如下:

const Koa = require('koa');
const app = new Koa();
const main = ctx => {
  ctx.response.body = 'Hello World';
};
app.use(main);
app.listen(3000);    //架設(shè)http服務(wù)器

app.listen()就是http.createServer(app.callback()).listen(...)的縮寫灵再。

main函數(shù)用來(lái)設(shè)置ctx.response.body。然后亿笤,使用app.use方法加載main函數(shù)檬嘀。
ctx.response代表 HTTP Response。同樣地责嚷,ctx.request代表 HTTP Request鸳兽。

Koa 默認(rèn)的返回類型是text/plain,如果想返回其他類型內(nèi)容罕拂,可以用ctx.request.accepts判斷一下揍异,客戶端希望接受什么數(shù)據(jù)(根據(jù) HTTP Request 的Accept字段)全陨,然后使用ctx.response.type指定返回類型。

const main = ctx => {
  if (ctx.request.accepts('xml')) {
    ctx.response.type = 'xml';
    ctx.response.body = '<data>Hello World</data>';
  } else if (ctx.request.accepts('json')) {
    ctx.response.type = 'json';
    ctx.response.body = { data: 'Hello World' };
  } else if (ctx.request.accepts('html')) {
    ctx.response.type = 'html';
    ctx.response.body = '<p>Hello World</p>';
  } else {
    ctx.response.type = 'text';
    ctx.response.body = 'Hello World';
  }
};

路由

原生路由

通過(guò)ctx.request.path可以獲取用戶請(qǐng)求的路徑衷掷,由此實(shí)現(xiàn)簡(jiǎn)單的路由辱姨。

const main = ctx => {
  if (ctx.request.path !== '/') {
    ctx.response.type = 'html';
    ctx.response.body = '<a href="/">Index Page</a>';
  } else {
    ctx.response.body = 'Hello World';
  }
};

koa-route 模塊

原生路由用起來(lái)不太方便,我們可以使用封裝好的[koa-route]模塊戚嗅。

const route = require('koa-route');
const about = ctx => {
  ctx.response.type = 'html';
  ctx.response.body = '<a href="/">Index Page</a>';
};
const main = ctx => {
  ctx.response.body = 'Hello World';
};
app.use(route.get('/', main));
app.use(route.get('/about', about));

上面代碼中雨涛,根路徑/的處理函數(shù)是main,/about路徑的處理函數(shù)是about懦胞。

靜態(tài)資源

靜態(tài)資源一個(gè)個(gè)寫路由很麻煩替久,也沒(méi)必要。[koa-static]模塊封裝了這部分的請(qǐng)求躏尉。

const path = require('path');
const serve = require('koa-static');
const main = serve(path.join(__dirname));
app.use(main);

重定向

服務(wù)器需要重定向(redirect)訪問(wèn)請(qǐng)求蚯根,ctx.response.redirect()方法可以發(fā)出一個(gè)302跳轉(zhuǎn),將用戶導(dǎo)向另一個(gè)路由胀糜。

const redirect = ctx => {
  ctx.response.redirect('/');
  ctx.response.body = '<a href="/">Index Page</a>';
};
app.use(route.get('/redirect', redirect));

中間件

const logger = (ctx, next) => {
  console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`);
  next();
}
app.use(logger);

上面代碼中的logger函數(shù)就叫做"中間件"(middleware)颅拦,因?yàn)樗幵?HTTP Request 和 HTTP Response 中間,用來(lái)實(shí)現(xiàn)某種中間功能教藻。app.use()用來(lái)加載中間件距帅。

Koa 所有的功能都是通過(guò)中間件實(shí)現(xiàn)的,默認(rèn)接受兩個(gè)參數(shù)括堤,第一個(gè)參數(shù)是 Context 對(duì)象碌秸,第二個(gè)參數(shù)是next函數(shù)。只要調(diào)用next函數(shù)痊臭,就可以把執(zhí)行權(quán)轉(zhuǎn)交給下一個(gè)中間件哮肚。
多個(gè)中間件會(huì)形成一個(gè)棧結(jié)構(gòu)(middle stack)登夫,以"先進(jìn)后出"(first-in-last-out)的順序執(zhí)行广匙。如果中間件內(nèi)部沒(méi)有調(diào)用next函數(shù),那么執(zhí)行權(quán)就不會(huì)傳遞下去恼策。

  • 最外層的中間件首先執(zhí)行鸦致。
  • 調(diào)用next函數(shù),把執(zhí)行權(quán)交給下一個(gè)中間件涣楷。
    ...
  • 最內(nèi)層的中間件最后執(zhí)行分唾。
  • 執(zhí)行結(jié)束后,把執(zhí)行權(quán)交回上一層的中間件狮斗。
    ...
  • 最外層的中間件收回執(zhí)行權(quán)之后绽乔,執(zhí)行next函數(shù)后面的代碼。

如果有異步操作(比如讀取數(shù)據(jù)庫(kù))碳褒,中間件就必須寫成 [async 函數(shù)]折砸。

const fs = require('fs.promised');
const Koa = require('koa');
const app = new Koa();
const main = async function (ctx, next) {
  ctx.response.type = 'html';
  ctx.response.body = await fs.readFile('./demos/template.html', 'utf8');
};
app.use(main);
app.listen(3000);

中間件的合成[koa-compose]模塊可以將多個(gè)中間件合成為一個(gè)看疗。

const middlewares = compose([logger, main]);
app.use(middlewares);

Koa 提供了ctx.throw()方法,用來(lái)拋出錯(cuò)誤睦授。ctx.throw(500)就是拋出500錯(cuò)誤两芳。

const main = ctx => {
  ctx.throw(500);
};

如果將ctx.response.status設(shè)置成404,就相當(dāng)于ctx.throw(404)去枷,返回404錯(cuò)誤怖辆。

const main = ctx => {
  ctx.response.status = 404;
  ctx.response.body = 'Page Not Found';
};

為了方便處理錯(cuò)誤,最好使用try...catch將其捕獲删顶。但是竖螃,為每個(gè)中間件都寫try...catch太麻煩,我們可以讓最外層的中間件翼闹,負(fù)責(zé)所有中間件的錯(cuò)誤處理斑鼻。

const handler = async (ctx, next) => {
  try {
    await next();  // 此處包住了后面內(nèi)部所有中間件
  } catch (err) {
    ctx.response.status = err.statusCode || err.status || 500;
    ctx.response.body = {
      message: err.message
    };
  }
};
const main = ctx => {
  ctx.throw(500);
};
app.use(handler);
app.use(main);

Web App 的功能

Cookies

ctx.cookies用來(lái)讀寫 Cookie。

const main = function(ctx) {
  const n = Number(ctx.cookies.get('view') || 0) + 1;
  ctx.cookies.set('view', n);
  ctx.response.body = n + ' views';
}
// 訪問(wèn) http://127.0.0.1:3000 猎荠,你會(huì)看到1 views坚弱。刷新一次頁(yè)面,就變成了2 views关摇。

表單就是 POST 方法發(fā)送到服務(wù)器的鍵值對(duì)荒叶。[koa-body]模塊可以用來(lái)從 POST 請(qǐng)求的數(shù)據(jù)體里面提取鍵值對(duì)。

const koaBody = require('koa-body');
const main = async function(ctx) {
  const body = ctx.request.body;
  if (!body.name) ctx.throw(400, '.name required');
  ctx.body = { name: body.name };
};
app.use(koaBody());

[koa-body]模塊還可以用來(lái)處理文件上傳输虱。請(qǐng)看:
https://github.com/ruanyf/jstutorial/blob/gh-pages/nodejs/koa.md 阮一峰教程

級(jí)聯(lián)式(Cascading)的結(jié)構(gòu)些楣,也就是說(shuō),屬于是層層調(diào)用宪睹,第一個(gè)中間件調(diào)用第二個(gè)中間件愁茁,第二個(gè)調(diào)用第三個(gè),以此類推亭病。上游的中間件必須等到下游的中間件返回結(jié)果鹅很,才會(huì)繼續(xù)執(zhí)行,這點(diǎn)很像遞歸罪帖。

Koa2與Koa1的區(qū)別

簡(jiǎn)單來(lái)說(shuō)就是async取代了星號(hào)* await取代了yield促煮;
async/await 的特點(diǎn):
可以讓異步邏輯用同步寫法實(shí)現(xiàn)
最底層的await返回需要是Promise對(duì)象
可以通過(guò)多層 async function 的同步寫法代替?zhèn)鹘y(tǒng)的callback嵌套

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封裝一下才能使用。
  • generator 中間件開發(fā)在koa v1和v2中使用
  • async await 中間件開發(fā)和只能在koa v2中使用
const Koa = require('koa')
const app = new Koa()
// 簡(jiǎn)單例子:
app.use( async ( ctx ) => {
  let url = ctx.request.url
  ctx.body = url
})
app.listen(3000)

koa-router中間件

npm/cnpm install --save 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')
})

官網(wǎng)技術(shù)說(shuō)明

Koa 依賴 node v7.6.0 或 ES2015及更高版本和 async 方法支持.
必修的 hello world 應(yīng)用:

const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
  ctx.body = 'Hello World';
});
app.listen(3000);

級(jí)聯(lián)通過(guò)一系列功能直接傳遞控制疾棵,直到一個(gè)返回盗飒,Koa 調(diào)用“下游”,然后控制流回“上游”陋桂。當(dāng)一個(gè)中間件調(diào)用 next() 則該函數(shù)暫停并將控制傳遞給定義的下一個(gè)中間件逆趣。當(dāng)在下游沒(méi)有更多的中間件執(zhí)行后,堆棧將展開并且每個(gè)中間件恢復(fù)執(zhí)行其上游行為嗜历。

app.use(function) 將給定的中間件方法添加到此應(yīng)用程序宣渗。
app.keys= 設(shè)置簽名的 Cookie 密鑰。

上下文(Context)

Koa Context 將 node 的 request 和 response 對(duì)象封裝到單個(gè)對(duì)象中梨州,為編寫 Web 應(yīng)用程序和 API 提供了許多有用的方法痕囱。 這些操作在 HTTP 服務(wù)器開發(fā)中頻繁使用。

app.use(async ctx => {
  ctx; // 這是 Context
  ctx.request; // 這是 koa Request
  ctx.response; // 這是 koa Response
});

為方便起見許多上下文的訪問(wèn)器和方法直接委托給它們的 ctx.request或 ctx.response暴匠。ctx.type 和 ctx.length 委托給 response 對(duì)象鞍恢,ctx.path 和 ctx.method 委托給 request。

ctx.req // Node 的 request 對(duì)象.
ctx.res // Node 的 response 對(duì)象.

繞過(guò) Koa 的 response 處理是不被支持的.應(yīng)避免使用以下 node 屬性:
res.statusCode
res.writeHead()
res.write()
res.end()

ctx.request koa 的 Request 對(duì)象.
ctx.response koa 的 Response 對(duì)象.

ctx.cookies.get(name, [options]) 通過(guò) options 獲取 cookie name:
signed 所請(qǐng)求的cookie應(yīng)該被簽名
ctx.cookies.set(name, value, [options])通過(guò) options 設(shè)置 cookie name 的 value :

  • maxAge 一個(gè)數(shù)字表示從 Date.now() 得到的毫秒數(shù)
  • signed cookie 簽名值
  • expires cookie 過(guò)期的 Date
  • path cookie 路徑, 默認(rèn)是'/'

request.header 請(qǐng)求標(biāo)頭對(duì)象每窖。
request.header = 設(shè)置請(qǐng)求標(biāo)頭對(duì)象帮掉。

request.method 請(qǐng)求方法。
request.method = 設(shè)置請(qǐng)求方法窒典,對(duì)于實(shí)現(xiàn)諸如 methodOverride() 的中間件是有用的蟆炊。

request.url 獲取請(qǐng)求 URL.
request.url = 設(shè)置請(qǐng)求 URL, 對(duì) url 重寫有用。

request.origin 獲取URL的來(lái)源瀑志,包括 protocol 和 host涩搓。
ctx.request.origin // => http://example.com

request.href 獲取完整的請(qǐng)求URL,包括 protocol劈猪,host 和 url昧甘。
ctx.request.href;// => http://example.com/foo/bar?q=1

request.path 獲取請(qǐng)求路徑名。

request.querystring 根據(jù) ? 獲取原始查詢字符串.

request.type 獲取請(qǐng)求 Content-Type 不含參數(shù) "charset"战得。
const ct = ctx.request.type;// => "image/png"

request.query 獲取解析的查詢字符串, 當(dāng)沒(méi)有查詢字符串時(shí)充边,返回一個(gè)空對(duì)象。
例如 "color=blue&size=small":

{
  color: 'blue',
  size: 'small'
}

request.accepts(types)
檢查給定的 type(s) 是否可以接受贡避,如果 true痛黎,返回最佳匹配予弧,否則為 false刮吧。

response.header 響應(yīng)標(biāo)頭對(duì)象。

response.status
獲取響應(yīng)狀態(tài)掖蛤。默認(rèn)情況下杀捻,response.status 設(shè)置為 404 而不是像 node 的 res.statusCode 那樣默認(rèn)為 200。

response.message 獲取響應(yīng)的狀態(tài)消息.
response.body 獲取響應(yīng)主體蚓庭。

response.redirect(url, [alt]) 執(zhí)行 [302] 重定向到 url.

response.type 獲取響應(yīng) Content-Type 不含參數(shù) "charset"致讥。
const ct = ctx.type;// => "image/png"

response.length
以數(shù)字返回響應(yīng)的 Content-Length仅仆,或者從ctx.body推導(dǎo)出來(lái),或者undefined垢袱。


起步填坑

項(xiàng)目生成器: npm install -g koa-generator
在你的工作目錄下墓拜,輸入:koa2 HelloKoa2
成功創(chuàng)建項(xiàng)目后,進(jìn)入項(xiàng)目目錄请契,并執(zhí)行npm install命令cd HelloKoa2 npm install
啟動(dòng)項(xiàng)目:npm start

koa聲明說(shuō)要在v3版本中取消對(duì)generator中間件的支持咳榜,所以為了長(zhǎng)久考慮還是用async語(yǔ)法的好。
如果想要繼續(xù)使用function*語(yǔ)法爽锥,可以使用 koa-convert 這個(gè)中間件進(jìn)行轉(zhuǎn)換涌韩。

const convert = require('koa-convert');
app.use(convert(bodyparser));
app.use(convert(json()));
app.use(convert(logger()));

Context封裝了node中的request和response。
koa@1.x使用this引用Context對(duì)象:

app.use(function *(){
  this.body = 'Hello World';
});

koa@2.x中使用ctx來(lái)訪問(wèn)Context對(duì)象:

app.use(async (ctx, next) => {
  await next();
  ctx.body = 'Hello World';
});

項(xiàng)目配置

這里的配置指的是運(yùn)行環(huán)境的配置氯夷,比如我們?cè)陂_發(fā)階段使用本地的數(shù)據(jù)庫(kù)臣樱,測(cè)試要使用測(cè)試庫(kù),發(fā)布上線時(shí)候使用線上的庫(kù)腮考,也會(huì)有不同的端口號(hào)雇毫。
npm start 就會(huì)運(yùn)行package.json中scripts對(duì)象對(duì)應(yīng)的start字段后面的內(nèi)容。

在npm中踩蔚,有四個(gè)常用的縮寫
npm start是npm run start
npm stop是npm run stop的簡(jiǎn)寫
npm test是npm run test的簡(jiǎn)寫
npm restart是npm run stop && npm run restart && npm run start的簡(jiǎn)寫

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末嘴拢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子寂纪,更是在濱河造成了極大的恐慌席吴,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捞蛋,死亡現(xiàn)場(chǎng)離奇詭異孝冒,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)拟杉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門庄涡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人搬设,你說(shuō)我怎么就攤上這事穴店。” “怎么了拿穴?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵泣洞,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我默色,道長(zhǎng)球凰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮呕诉,結(jié)果婚禮上缘厢,老公的妹妹穿的比我還像新娘。我一直安慰自己甩挫,他們只是感情好贴硫,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著伊者,像睡著了一般夜畴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上删壮,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天贪绘,我揣著相機(jī)與錄音,去河邊找鬼央碟。 笑死税灌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的亿虽。 我是一名探鬼主播菱涤,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼洛勉!你這毒婦竟也來(lái)了粘秆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤收毫,失蹤者是張志新(化名)和其女友劉穎攻走,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體此再,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡昔搂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了输拇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摘符。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖策吠,靈堂內(nèi)的尸體忽然破棺而出逛裤,到底是詐尸還是另有隱情,我是刑警寧澤猴抹,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布带族,位于F島的核電站,受9級(jí)特大地震影響洽糟,放射性物質(zhì)發(fā)生泄漏炉菲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一坤溃、第九天 我趴在偏房一處隱蔽的房頂上張望拍霜。 院中可真熱鬧,春花似錦薪介、人聲如沸祠饺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)道偷。三九已至,卻和暖如春记劈,著一層夾襖步出監(jiān)牢的瞬間勺鸦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工目木, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留换途,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓刽射,卻偏偏與公主長(zhǎng)得像军拟,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子誓禁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

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