Node.js系列八 - koa開發(fā)web服務(wù)器

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 

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末残拐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子碟嘴,更是在濱河造成了極大的恐慌吝羞,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件塞赂,死亡現(xiàn)場(chǎng)離奇詭異季俩,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)雀瓢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門枢析,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人刃麸,你說(shuō)我怎么就攤上這事醒叁。” “怎么了泊业?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵把沼,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我吁伺,道長(zhǎng)饮睬,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任篮奄,我火速辦了婚禮捆愁,結(jié)果婚禮上割去,老公的妹妹穿的比我還像新娘。我一直安慰自己昼丑,他們只是感情好呻逆,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著菩帝,像睡著了一般咖城。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上胁附,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天酒繁,我揣著相機(jī)與錄音,去河邊找鬼控妻。 笑死州袒,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的弓候。 我是一名探鬼主播郎哭,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼菇存!你這毒婦竟也來(lái)了夸研?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤依鸥,失蹤者是張志新(化名)和其女友劉穎亥至,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贱迟,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡姐扮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了衣吠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茶敏。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖缚俏,靈堂內(nèi)的尸體忽然破棺而出惊搏,到底是詐尸還是另有隱情,我是刑警寧澤忧换,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布恬惯,位于F島的核電站,受9級(jí)特大地震影響包雀,放射性物質(zhì)發(fā)生泄漏宿崭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一才写、第九天 我趴在偏房一處隱蔽的房頂上張望葡兑。 院中可真熱鬧,春花似錦赞草、人聲如沸讹堤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)洲守。三九已至,卻和暖如春沾凄,著一層夾襖步出監(jiān)牢的瞬間梗醇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工撒蟀, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留叙谨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓保屯,卻偏偏與公主長(zhǎng)得像手负,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子姑尺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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