開始
就像官網上說的,一切框架都從一個"Hello World"開始,首先我們新建一個 package.json,內容盡量簡單:
{
"name": "koa-note",
"description": "Koa 學習筆記",
"main": "index.js"
}
然后 npm 安裝 Koa
npm i koa
將官網上給的示例粘貼進去:
const Koa = require('koa');
const app = new Koa();
app.use(ctx => {
ctx.body = 'Hello World';
});
app.listen(4000);
然后執行 node --harmony index.js
,就可以在瀏覽器中訪問 http://localhost:4000/
了。
注1:ctx 是 context 的簡寫,下面詳細介紹。
注2:示例源碼。
2個關鍵點
Koa 的核心設計思路是為中間件層提供高級語法糖封裝,以增強其互用性和健壯性,並使得編寫中間件變得相當有趣。Koa 應用是一個包含一系列中間件 generator 函數的對象。
中間件級聯
Koa 通過 generators 來實現“真正”的中間件。 Connect 簡單地將控制權交給一系列函數來處理,直到函數返回。 與之不同,當執行到 yield next 語句時,Koa 暫停了該中間件,繼續執行下一個符合請求的中間件('downstrem'),然后控制權再逐級返回給上層中間件('upstream')。
const Koa = require('koa');
const app = new Koa();
// 定制請求頭
app.use(async function (ctx, next) {
const start = new Date();
await next();
const ms = new Date() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
// 日志
app.use(async function (ctx, next) {
const start = new Date();
await next();
const ms = new Date() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}`);
});
// 請求內容
app.use(ctx => {
ctx.body = 'Hello World';
});
上面的例子在頁面中返回 "Hello World",然而當請求開始時,請求先經過定制請求頭和日志中間件,並記錄中間件執行起始時間。 然后將控制權交給 reponse 中間件。當中間件運行到 yield next 時,函數掛起並將控制前交給下一個中間件。當沒有中間件執行 yield next 時,程序棧會逆序喚起被掛起的中間件來執行接下來的代碼。
為了方便理解我 YY 了下面的例子:
// 定制請求頭
app.use(async function (ctx, next) {
console.log('step 1');
await next();
console.log('step 5');
});
// 日志輸出
app.use(async function (ctx, next) {
console.log('step 2');
await next();
console.log('step 4:');
});
// 請求內容
app.use(ctx => {
console.log('step 3');
});
注:示例源碼。
說到中間件就不得不提到中間件的開發,簡單地說中間件就是一個回調函數,中間件的原理可以參考下面這個"洋蔥模型":
2.x 版可以使用 yield
來分割 request 和 response,但是 2.0 發版后就明確說明在 3.x 希望使用 await
來代替 yield
,也就是要從 generator function 升級到 async function。某些組件還沒有進行升級,經常會看到控制台上又這樣的警告信息 "Support for generators will be removed in v3.",其中常用的 koa-router 和 koa-proxy 就在其中,如果想去除警告,並且與下一個版本兼容,可以參考 koa-static 這個庫(做靜態文件路由的一個中間件)。
app 的幾個方法
app.listen(),為應用綁定端口,參數的詳細文檔請查看nodejs.org。
app.callback(),返回一個適合 http.createServer()
方法的回調函數用來處理請求。
app.use(function),為應用添加指定的中間件,詳情請看 Middleware。
app.keys=,設置簽名Cookie密鑰。
app.context,方便擴展 ctx:
app.context.db = db();
app.use(async (ctx) => {
console.log(ctx.db);
});
app.on,典型的是錯誤處理:
app.on('error', function(err){
log.error('server error', err);
});
上下文
Koa Context 將 node 的 request 和 response 對象封裝在一個單獨的對象里面,其為編寫 web 應用和 API 提供了很多有用的方法。
app.use(function *(){
this; // is the Context
this.request; // is a koa Request
this.response; // is a koa Response
});
一堆 API 就不寫了,自行到官網查看。
中間件
Koa 就是一個框架,大部分功能還需要靠中間件實現。
中間件 koa-router
安裝
npm install koa-router
使用
const Koa = require('koa');
const app = new Koa();
const router = require('koa-router')();
router.get('/', function *(next) {
this.body = 'Hello World!';
});
router.get('/a', function *(next) {
this.body = 'Hello World A!';
});
app.use(router.routes());
app.listen(4000);
console.log('服務已啟動: localhost:4000');
RESTFul 風格的路由像這樣配置:
router.get('/users/:id', function *(next) {
// ...
}).del('/users/:id', function *(next) {
// ...
});
官網:koa-router。
中間件 koa-static
安裝
npm i koa-static --save
使用
const koaStatic = require('koa-static')('./');
app.use(koaStatic);
說明:
- 第一個參數指定根路徑
- 第二個參數指定各種配置項
注意:
默認請求指向 index.html
文件,當然你可以通過第二個參數 options 自定義默認請求的文件。如果配置了 koa-router
的默認路徑那么靜態文件的路由默認會失效。如下面訪問 http://localhost:4000/
這樣的路徑會返回報 404,而不會去讀取 ../dist/index.html
文件並返回。
router.get('/', function *(next) {
});
const koaStatic = require('koa-static')('./', {
index: '../dist/index.html'
});
其他參數參考 koa-static 中間件官網:koa-static。
注:示例源碼,示例驗證了 HTML,圖片,CSS 和 JS 靜態文件的加載。
中間件 koa-proxy
安裝
npm i koa-proxy --save
代理接口,默認只代理接口不代理靜態文件,當前的 router
優先,也就是說如果已經配置了某接口的路由,那么此接口不會被代理帶其他服務器上。
const koaProxy = require('koa-proxy')({
host: 'http://127.0.0.1:5000'
});
app.use(koaProxy);
也可以給靜態文件做遠程代理:
app.get('index.js', proxy({
url: 'http://127.0.0.1:5000/index.js'
}));
注1:示例源碼。 注2:中間件 koa-proxy
意外收獲的包
讀源碼的時候發現了很多服務器開發有用的包,下面列一下:
co
Generator 函數執行器,TJ大神的作品。GitHub
co(gen);
methods
Node 支持的 http 類型,這種只返回一個數組的小包居然也是 TJ 大神創立的。GitHub
assert
對 Node 原生包的擴展,支持瀏覽器。GitHub
// 判空
assert(root, 'root directory is required to serve files');
http-errors
處理 http 異常的模塊,這個做服務器端開發肯定少不了。GitHub
cookie
這種包用途也很廣泛。GitHub
path-to-regexp
非常棒的路徑匹配和 RESTFul 地址轉化的工具。GitHub
is-generator-function
判斷一個函數是否是 Generator 函數。GitHub
const isG = require('is-generator-function');
isG(fn);