Koa2 入門教程

完整Demo地址

裏面demo都是本身寫的,保證能跑,至於環境問題我就不敢保證了。懶得寫就去上面搬走看,懶得搬就直接看文章,大部分代碼連輸出信息都給大家了。
koa-demojavascript

官網介紹

koa 是由 Express 原班人馬打造的,致力於成爲一個更小、更富有表現力、更健壯的 Web 框架。 使用 koa 編寫 web 應用,經過組合不一樣的 generator,能夠免除重複繁瑣的回調函數嵌套, 並極大地提高錯誤處理的效率。koa 不在內核方法中綁定任何中間件, 它僅僅提供了一個輕量優雅的函數庫,使得編寫 Web 應用變得駕輕就熟。css

前期準備

咱們首先安裝一些必要庫先,根據我的選擇可使用yarn,cnpm,或者npm都行。html

KOA框架java

yarn add koa

這還不夠,由於 Koa 依賴 node v7.6.0 或 ES2015及更高版本和 async 方法支持.大家能夠根據自身須要安裝node

Babel registergit

transform-async-to-generator 或 transform-async-to-module-method

由於我用到 Nodejs10.0,因此不須要安裝這些東西,就不說了。github

簡單入門

慣例拿建立應用程序做爲一個框架的入門例子。web

const Koa = require('koa'),
  app = new Koa();

app
  .use(async ctx => {
    ctx.body = '暗號:Hello World';
  })
  .listen(3000);

console.log('已創建鏈接,效果請看http://127.0.0.1:3000/');

代碼一目瞭然,不廢話了。
(完整代碼能夠執行koa-demo的 lesson1 查看效果)npm

Favicon.ico

所謂 favicon,即 Favorites Icon 的縮寫,顧名思義,即是其可讓瀏覽器的收藏夾中除顯示相應的標題外,還以圖標的方式區別不一樣的網站。固然,這不是Favicon的所有,根據瀏覽器的不一樣,Favicon顯示也有所區別:在大多數主流瀏覽器如FireFox和Internet Explorer (5.5及以上版本)中,favicon不只在收藏夾中顯示,還會同時出如今地址欄上,這時用戶能夠拖曳favicon到桌面以創建到網站的快捷方式;除此以外,標籤式瀏覽器甚至還有很多擴展的功能,如FireFox甚至支持動畫格式的favicon等。
問題在於這裡代碼瀏覽器會自動發起請求網站根目錄的這個圖標,干擾測試,因此接下來的打印結果你們無視Favicon.ico請求就好。json

級聯

Koa 應用程序是一個包含一組中間件函數的對象,它是按照相似堆棧的方式組織和執行的。
當一箇中間件調用 next() 則該函數暫停並將控制傳遞給定義的下一個中間件。當在下游沒有更多的中間件執行後,堆棧將展開而且每一箇中間件恢復執行其上游行爲。(用一種比較類似的比喻就是中間件至關於一次DOM事件,從事件捕捉到事件冒泡的過程)。

const Koa = require('koa'),
  app = new Koa();

// 一層中間件
app.use((ctx, next) => {
  console.log('請求資源:' + ctx.url);
  console.log('一層中間件控制傳遞下去');
  next();
  console.log('一層中間件控制傳遞回來');
  ctx.body = '暗號:Day Day Up';
});

// 二層中間件
app.use((ctx, next) => {
  console.log('二層中間件控制傳遞下去');
  next();
  console.log('二層中間件控制傳遞回來');
});

// response
app.use(ctx => {
  console.log('輸出body');
  ctx.body = '暗號:Good Good Study';
});

app.listen(3000);
console.log('已創建鏈接,效果請看http://127.0.0.1:3000/');

// 一層中間件控制傳遞下去
// 二層中間件控制傳遞下去
// 輸出body
// 二層中間件控制傳遞回來
// 一層中間件控制傳遞回來

(完整代碼能夠執行koa-demo的 lesson2 查看效果)

從上面結果能夠看出每請求一次資源都會通過全部中間件,而且在執行到最尾部中間件時候會將控制權反向傳遞,輸出結果是頭部的body覆蓋尾部的body。
說實話沒試過這種方式,有點不習慣。

上下文(Context)

Koa Context 將 Nodejs 的 requestresponse 對象封裝到單個對象中,每一個請求都將建立一個 Context,並在中間件中做爲接收器引用,或者 ctx 標識符,許多上下文的訪問器和方法直接委託給它們的 ctx.requestctx.response
咱們能夠直接打印出來ctx對象看看有什麼。

const Koa = require('koa'),
  app = new Koa();

// response
app.use(async ctx => {
  console.log('ctx:', ctx);
});

app.listen(3000);
console.log('已創建鏈接,效果請看http://127.0.0.1:3000/');

/*
ctx: { request:
   { method: 'GET',
     url: '/',
     header:
      { host: 'localhost:3000',
        connection: 'keep-alive',
        'cache-control': 'max-age=0',
        'upgrade-insecure-requests': '1',
        'user-agent':
         'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36',
        accept:
         'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*!/!*;q=0.8',
        'accept-encoding': 'gzip, deflate, sdch',
        'accept-language': 'zh-CN,zh;q=0.8',
        cookie:
         'loginInfo={"username":"abc","password":"MjIyMjIy","rememberMe":1}' } },
  response: { status: 404, message: 'Not Found', header: {} },
  app: { subdomainOffset: 2, proxy: false, env: 'development' },
  originalUrl: '/',
  req: '<original node req>',
  res: '<original node res>',
  socket: '<original node socket>' }*/

(完整代碼能夠執行koa-demo的 lesson3 查看效果)

請求(Request)

Koa Request 對象是在 Nodejs 的 vanilla 請求對象之上的抽象,提供了諸多對 HTTP 服務器開發有用的功能。Koa的 Request 對象包括由 acceptsnegotiator 提供的有用的內容協商實體。

  • request.accepts(types)
  • request.acceptsEncodings(types)
  • request.acceptsCharsets(charsets)
  • request.acceptsLanguages(langs)
  1. 若是沒有提供類型,則返回全部可接受的類型;
  2. 若是提供多種類型,將返回最佳匹配;
  3. 若是沒有找到匹配項,則返回一個false;

由於用法都一個樣,挑選一個來說解。

request.accepts(types)

檢查給定的類型是否能夠接受,type 值多是一個或多個 mime 類型的字符串或數組,若是能夠就返回最佳匹配類型字符串,不然返回false。

const Koa = require('koa'),
  app = new Koa();

app
  .use(async ctx => {
    switch (ctx.accepts('json', 'html', 'text')) {
      case 'json':
        ctx.type = 'json';
        ctx.body = '<p>匹配類型json</p>';
        break;
      case 'html':
        ctx.type = 'html';
        ctx.body = '<p>匹配類型html</p>';
        break;
      case 'text':
        ctx.type = 'text';
        ctx.body = '<p>匹配類型text</p>';
        break;
      default:
        ctx.throw(406, 'json, html, or text only');
    }
  })
  .listen(3000);
console.log('已創建鏈接,效果請看http://127.0.0.1:3000/');

(完整代碼能夠執行koa-demo的 lesson4 查看效果)

實際開發中須要更多不一樣的處理細節,因此咱們能夠把內容設置成模板template1.html引用。

<!DOCTYPE html>
<html lang="en" dir="ltr">

  <head>
    <meta charset="utf-8">
    <title></title>
  </head>

  <body>
    <p>匹配類型html</p>
  </body>

</html>

(完整代碼能夠執行koa-demo的 template1 查看效果)

const Koa = require('koa'),
  fs = require('fs'),
  app = new Koa();

app
  .use(async ctx => {
    switch (ctx.accepts('json', 'html', 'text')) {
      case 'html':
        ctx.type = 'html';
        ctx.body = fs.createReadStream('./template1.html');
        break;
      default:
        ctx.throw(406, 'json, html, or text only');
    }
  })
  .listen(3000);
console.log('已創建鏈接,效果請看http://127.0.0.1:3000/');

(完整代碼能夠執行koa-demo的 lesson5 查看效果)

路由

其實咱們上面的代碼已是原始路由的用法了,咱們增長請求資源的判斷就能夠了,另外新增一個template2.html模板切換看效果

<!DOCTYPE html>
<html lang="en" dir="ltr">

  <head>
    <meta charset="utf-8">
    <title></title>
  </head>

  <body>
    <p>template2</p>
  </body>

</html>

(完整代碼能夠執行koa-demo的 template2 查看效果)
而後咱們去掉類型判斷等代碼看效果,直接寫死html便可,否則type默認爲空,打開頁面會觸發下載的,不信大家去掉設置類型那行代碼看看。

const Koa = require('koa'),
  fs = require('fs'),
  app = new Koa();

app
  .use(async ctx => {
    console.log('type: ' + ctx.type);
    switch (ctx.url) {
      case '/':
        ctx.type = 'html';
        ctx.body = fs.createReadStream('./template1.html');
        break;
      case '/template2':
        ctx.type = 'html';
        ctx.body = fs.createReadStream('./template2.html');
        break;
      default:
        ctx.throw(406, 'json, html, or text only');
    }
  })
  .listen(3000);
console.log('已創建鏈接,效果請看http://127.0.0.1:3000/');

(完整代碼能夠執行koa-demo的 lesson6 查看效果)
執行腳本以後會默認看到template2.html模板內容,手動換成http://127.0.0.1:3000/template2若是沒設置type在Chrome會看到下載彈窗,其餘瀏覽器沒試過。

實際開發咱們不會這麼繁瑣的去區分路由,上面說過 koa 不在內核方法中綁定任何中間件,它僅僅提供了一個輕量優雅的函數庫,因此咱們須要安裝一個路由中間件。
koa-route3.2.0 ,上次推送已是兩年前了,若是不是放棄維護就是已經很穩定了。

yarn add koa-route

若是你須要使用完整特性的路由庫能夠看 koa-router
這裏簡單展現 koa-route 用法。

const Koa = require('koa'),
  _ = require('koa-route'),
  fs = require('fs'),
  app = new Koa();

const route = {
  index: ctx => {
    //doSomethings
    ctx.type = 'html';
    ctx.body = fs.createReadStream('./template1.html');
  },
  template2: ctx => {
    //doSomethings
    ctx.type = 'html';
    ctx.body = fs.createReadStream('./template2.html');
  },
};

app
  .use(_.get('/', route.index))
  .use(_.get('/template2', route.template2))
  .listen(3000);
console.log('已創建鏈接,效果請看http://127.0.0.1:3000/');

(完整代碼能夠執行koa-demo的 lesson7 查看效果)

響應狀態

若是代碼運行過程當中發生錯誤,咱們須要把錯誤信息返回給用戶。

ctx.throw([status], [msg], [properties])

等價於

const err = new Error(msg);
err.status = status;
err.expose = true;
throw err;

注意:這些是用戶級錯誤,並用 err.expose 標記,這意味着消息適用於客戶端響應,這一般不是錯誤消息的內容,由於您不想泄漏故障詳細信息。

100 "continue"
101 "switching protocols"
102 "processing"
200 "ok"
201 "created"
202 "accepted"
203 "non-authoritative information"
204 "no content"
205 "reset content"
206 "partial content"
207 "multi-status"
208 "already reported"
226 "im used"
300 "multiple choices"
301 "moved permanently"
302 "found"
303 "see other"
304 "not modified"
305 "use proxy"
307 "temporary redirect"
308 "permanent redirect"
400 "bad request"
401 "unauthorized"
402 "payment required"
403 "forbidden"
404 "not found"
405 "method not allowed"
406 "not acceptable"
407 "proxy authentication required"
408 "request timeout"
409 "conflict"
410 "gone"
411 "length required"
412 "precondition failed"
413 "payload too large"
414 "uri too long"
415 "unsupported media type"
416 "range not satisfiable"
417 "expectation failed"
418 "I'm a teapot"
422 "unprocessable entity"
423 "locked"
424 "failed dependency"
426 "upgrade required"
428 "precondition required"
429 "too many requests"
431 "request header fields too large"
500 "internal server error"
501 "not implemented"
502 "bad gateway"
503 "service unavailable"
504 "gateway timeout"
505 "http version not supported"
506 "variant also negotiates"
507 "insufficient storage"
508 "loop detected"
510 "not extended"
511 "network authentication required"

狀態碼錯誤

有兩種寫法,ctx.throw(狀態碼) 或者 ctx.status = 狀態碼 ,它們都會自動返回默認文字信息,區別在於二者設置返回信息的方式。
注意:默認狀況下,response.status 設置爲 404 而不是像 node 的 res.statusCode 那樣默認爲 200。

const Koa = require('koa'),
  _ = require('koa-route'),
  app = new Koa();

const router = {
  '403': ctx => {
    //doSomethings
    ctx.throw(403, '403啦!');
  },
  '404': ctx => {
    //doSomethings
    ctx.status = 404;
    ctx.body = `<p>404啦!</p>`;
  },
};

app
  .use(_.get('/403', router[403]))
  .use(_.get('/404', router[404]))
  .listen(3000);
console.log('已創建鏈接,效果請看http://127.0.0.1:3000/');

(完整代碼能夠執行koa-demo的 lesson8 查看效果)
大家能夠分別打開 http://localhost:3000/403http://localhost:3000/404 看輸出結果。

錯誤監聽

const Koa = require('koa'),
  _ = require('koa-route'),
  app = new Koa();

const router = {
  index: ctx => {
    //doSomethings
    ctx.throw(500, '我是故意的!');
  },
};

app
  .use(_.get('/', router.index))
  .on('error', (err, ctx) => {
    console.error('error', err);
  })
  .listen(3000);
console.log('已創建鏈接,效果請看http://127.0.0.1:3000/');
/*
error { InternalServerError: 我是故意的!
    at Object.throw (C:\project\test\koa-demo\node_modules\koa\lib\context.js:93:11)
    at Object.index (C:\project\test\koa-demo\lesson9.js:8:18)
    at C:\project\test\koa-demo\node_modules\koa-route\index.js:39:44
    at dispatch (C:\project\test\koa-demo\node_modules\koa-compose\index.js:42:32)
    at C:\project\test\koa-demo\node_modules\koa-compose\index.js:34:12
    at Application.handleRequest (C:\project\test\koa-demo\node_modules\koa\lib\application.js:150:12)
    at Server.handleRequest (C:\project\test\koa-demo\node_modules\koa\lib\application.js:132:19)
    at Server.emit (events.js:182:13)
    at parserOnIncoming (_http_server.js:654:12)
    at HTTPParser.parserOnHeadersComplete (_http_common.js:109:17) message: '我是故意的!' }*/

(完整代碼能夠執行koa-demo的 lesson9 查看效果)

錯誤捕捉

你也能直接使用try...catch()直接處理,可是「error」監聽事件就不會再接收該錯誤信息。

const Koa = require('koa'),
  app = new Koa();

const err = async (ctx, next) => {
    try {
      await next();
    } catch (err) {
      ctx.status = 404;
      ctx.body = `<p>你看看終端有沒打印錯誤!</p>`;
    }
  },
  index = ctx => {
    ctx.throw(500);
  };

app
  .use(err)
  .use(index)
  .on('error', function(err) {
    console.error('error', err);
  })
  .listen(3000);
console.log('已創建鏈接,效果請看http://127.0.0.1:3000/');

(完整代碼能夠執行koa-demo的 lesson10 查看效果)
注意,這裡的錯誤處理若是使用ctx.throw()方法的話能被「error」事件監聽到,不是因為該方法會再拋出新的錯誤。

const Koa = require('koa'),
  app = new Koa();

const err = async (ctx, next) => {
    try {
      await next();
    } catch (err) {
      ctx.throw(404, '你看看終端有沒打印錯誤!');
    }
  },
  index = ctx => {
    ctx.throw(500);
  };

app
  .use(err)
  .use(index)
  .on('error', function(err) {
    console.error('error', err);
  })
  .listen(3000);
console.log('已創建鏈接,效果請看http://127.0.0.1:3000/');

(完整代碼能夠執行koa-demo的 lesson1 查看效果)1
若是想同時觸發錯誤監聽,KOA也提供了 emit 方法能夠實現。

const Koa = require('koa'),
  app = new Koa();

const err = async (ctx, next) => {
    try {
      await next();
    } catch (err) {
      ctx.status = 404;
      ctx.body = `<p>你看看終端有沒打印錯誤!</p>`;
      ctx.app.emit('error', err, ctx);
    }
  },
  index = ctx => {
    ctx.throw(500);
  };

app
  .use(err)
  .use(index)
  .on('error', function(err) {
    console.error('error', err);
  })
  .listen(3000);
console.log('已創建鏈接,效果請看http://127.0.0.1:3000/');

/*
error { InternalServerError: Internal Server Error
    at Object.throw (C:\project\test\koa-demo\node_modules\koa\lib\context.js:93:11)
    at index (C:\project\test\koa-demo\lesson12.js:14:18)
    at dispatch (C:\project\test\koa-demo\node_modules\koa-compose\index.js:42:32)
    at err (C:\project\test\koa-demo\lesson12.js:6:19)
    at dispatch (C:\project\test\koa-demo\node_modules\koa-compose\index.js:42:32)
    at C:\project\test\koa-demo\node_modules\koa-compose\index.js:34:12
    at Application.handleRequest (C:\project\test\koa-demo\node_modules\koa\lib\application.js:150:12)
    at Server.handleRequest (C:\project\test\koa-demo\node_modules\koa\lib\application.js:132:19)
    at Server.emit (events.js:182:13)
    at parserOnIncoming (_http_server.js:654:12) message: 'Internal Server Error' }
*/

(完整代碼能夠執行koa-demo的 lesson11 查看效果)

靜態資源

聰明的人在上面代碼就能看出一些問題,還記得咱們說過忽略 Favicon.ico 的請求麼。咱們不只僅有頁面的請求,還有其餘資源的請求。
咱們如今仍是經過url判斷返回頁面,若是是其餘靜態資源如圖片那些又怎麼辦?這裡介紹一下依賴庫koa-static5.0.0

yarn add koa-static
--------------------------
require('koa-static')(root, opts)

經過設置根目錄和可選項會配置靜態資源查找路徑,咱們先建立一個img目錄存放一張圖片,而後在 template3.html 引用,再設置路徑 require('koa-static')(__dirname + '/img/'),它會自動到指定目錄下查找資源。

<!DOCTYPE html>
<html lang="en" dir="ltr">

  <head>
    <meta charset="utf-8">
    <title></title>
  </head>

  <body>
    <p>沒錯,我就是首頁</p>
    <img src="./1.gif"/>
  </body>

</html>

(完整代碼能夠執行koa-demo的 template3 查看效果)

const Koa = require('koa'),
  _ = require('koa-route'),
  serve = require('koa-static')(__dirname + '/img/'),
  fs = require('fs'),
  app = new Koa();

const router = {
  index: ctx => {
    //doSomethings
    ctx.type = 'html';
    ctx.body = fs.createReadStream('./template3.html');
  },
};

app
  .use(serve)
  .use(_.get('/', router.index))
  .listen(3000);
console.log('已創建鏈接,效果請看http://127.0.0.1:3000/');

(完整代碼能夠執行koa-demo的 lesson13 查看效果)
若是你仍是有些不懂的話修改下路徑,require('koa-static')(__dirname),而後圖片地址換成 "./img/1.gif" 。你就看到仍是能找到對應資源。

中間件管理

隨着項目開發你可能會安裝愈來愈多的中間件,全部可使用koa-compose作中間件管理。這個不少庫都有相似的中間件,用於簡化中間件的使用。
上面咱們用來說解 koa 級聯的那個例子能夠直接拿來修改使用。

const Koa = require('koa'),
  compose = require('koa-compose'),
  app = new Koa();

// 一層中間
const mid1 = (ctx, next) => {
  console.log('請求資源:' + ctx.url);
  console.log('一層中間件控制傳遞下去');
  next();
  console.log('一層中間件控制傳遞回來');
};

// 二層中間
const mid2 = (ctx, next) => {
  console.log('二層中間件控制傳遞下去');
  next();
  console.log('二層中間件控制傳遞回來');
};

// response
const mid3 = ctx => {
  console.log('輸出body');
  ctx.body = '暗號:Hello World';
};

app.use(compose([mid1, mid2, mid3])).listen(3000);
console.log('已創建鏈接,效果請看http://127.0.0.1:3000/');
// 請求資源:/
// 一層中間件控制傳遞下去
// 二層中間件控制傳遞下去
// 輸出body
// 二層中間件控制傳遞回來
// 一層中間件控制傳遞回來

(完整代碼能夠執行koa-demo的 lesson14 查看效果)
能夠看出大概原理就是把引用多箇中間件的使用方式改爲將多箇中間件組裝成一個使用。

請求處理

咱們處理請求的時候能夠用 koa-body 解析請求體。

A full-featured koa body parser middleware. Support multipart, urlencoded and json request bodies. Provides same functionality as Express's bodyParser - multer. And all that is wrapped only around co-body and formidable.

一個功能豐富的body解析中間件,支持多部分,urlencoded,json請求體,提供Express裏bodyParse同樣的函數方法

直接安裝依賴

yarn add koa-body

新建一個提交頁面

<!DOCTYPE html>
<html lang="en" dir="ltr">

  <head>
    <meta charset="utf-8">
    <title></title>
  </head>

  <body>
    <form class="" action="/upload" method="post">
      <input type="text" name="name" value="">
      <button type="submit" name="button">提交</button>
    </form>
  </body>

</html>

(完整代碼能夠執行koa-demo的 template4 查看效果)

能夠輸出格式看看效果

const Koa = require('koa'),
  koaBody = require('koa-body'),
  _ = require('koa-route'),
  fs = require('fs'),
  app = new Koa();

const router = {
    index: ctx => {
      //doSomethings
      ctx.type = 'html';
      ctx.body = fs.createReadStream('./template4.html');
    },
  },
  upload = ctx => {
    console.log(ctx.request.body);
    ctx.body = `Request Body: ${JSON.stringify(ctx.request.body)}`;
  };

app
  .use(koaBody())
  .use(_.get('/', router.index))
  .use(_.post('/upload', upload))
  .listen(3000);
console.log('已創建鏈接,效果請看http://127.0.0.1:3000/');

(完整代碼能夠執行koa-demo的 lesson15 查看效果)
提交內容以後在頁面和終端都能看到body內容。

參考資料

Koa (koajs)
Koa examples
Koa 框架教程

相關文章
相關標籤/搜索