Koa1 框架

安裝建立項目:javascript

1.必定要全局安裝(koa1.2和koa2都己經支持)java

npm install koa-generator -g 

2.
koa1 生成一個test項目,切到test目錄並下載依賴node

koa1建立項目mysql

koa test
cd test 
npm install
運行:npm start
訪問:http://localhost:3000

Koa是一個相似於Express的Web開發框架,創始人也是同一我的。它的主要特色是,使用了ES6的Generator函數,進行了架構的從新設計。也就是說,Koa的原理和內部結構很像Express,可是語法和內部結構進行了升級。git

官方faq有這樣一個問題:」爲何koa不是Express 4.0?「,回答是這樣的:」Koa與Express有很大差別,整個設計都是不一樣的,因此若是將Express 3.0按照這種寫法升級到4.0,就意味着重寫整個程序。因此,咱們以爲創造一個新的庫,是更合適的作法。「github

Koa應用

一個Koa應用就是一個對象,包含了一個middleware數組,這個數組由一組Generator函數組成。這些函數負責對HTTP請求進行各類加工,好比生成緩存、指定代理、請求重定向等等。sql

1 var koa = require('koa');
2 var app = koa();
3 
4 app.use(function *(){
5   this.body = 'Hello World';
6 });
7 
8 app.listen(3000);
要安裝koa才能測試

上面代碼中,變量app就是一個Koa應用。它監聽3000端口,返回一個內容爲Hello World的網頁。express

app.use方法用於向middleware數組添加Generator函數。npm

listen方法指定監聽端口,並啓動當前應用。它實際上等同於下面的代碼數組

1 var http = require('http');
2 var koa = require('koa');
3 var app = koa();
4 http.createServer(app.callback()).listen(3000);

中間件

下面是一個兩個中間件級聯的例子

1 app.use(function *() {
2   this.body = "header\n";
3   yield saveResults.call(this);
4   this.body += "footer\n";
5 });
6 
7 function *saveResults() {
8   this.body += "Results Saved!\n";
9 }

上面代碼中,第一個中間件調用第二個中間件saveResults,它們都向this.body寫入內容。最後,this.body的輸出以下。

 1 header 2 Results Saved! 3 footer 

只要有一箇中間件缺乏yield next語句,後面的中間件都不會執行,這一點要引發注意。

若是想跳過一箇中間件,能夠直接在該中間件的第一行語句寫上return yield next

1 app.use(function* (next) {
2   if (skip) return yield next;
3 })

路由

能夠經過this.path屬性,判斷用戶請求的路徑,從而起到路由做用。

 1 app.use(function* (next) {
 2   if (this.path === '/') {
 3     this.body = 'we are at home!';
 4   } else {
 5     yield next;
 6   }
 7 })
 8 
 9 // 等同於
10 
11 app.use(function* (next) {
12   if (this.path !== '/') return yield next;
13   this.body = 'we are at home!';
14 })

下面是多路徑的例子。

 1 let koa = require('koa')
 2 
 3 let app = koa()
 4 
 5 // normal route
 6 app.use(function* (next) {
 7   if (this.path !== '/') {
 8     return yield next
 9   }
10 
11   this.body = 'hello world'
12 });
13 
14 // /404 route
15 app.use(function* (next) {
16   if (this.path !== '/404') {
17     return yield next;
18   }
19 
20   this.body = 'page not found'
21 });
22 
23 // /500 route
24 app.use(function* (next) {
25   if (this.path !== '/500') {
26     return yield next;
27   }
28 
29   this.body = 'internal server error'
30 });
31 
32 app.listen(8080)

上面代碼中,每個中間件負責一個路徑,若是路徑不符合,就傳遞給下一個中間件。

複雜的路由須要安裝koa-router插件。

 1 var app = require('koa')();
 2 var Router = require('koa-router');
 3 
 4 var myRouter = new Router();
 5 
 6 myRouter.get('/', function *(next) {
 7   this.response.body = 'Hello World!';
 8 });
 9 
10 app.use(myRouter.routes());
11 
12 app.listen(3000);

上面代碼對根路徑設置路由。

Koa-router實例提供一系列動詞方法,即一種HTTP動詞對應一種方法。典型的動詞方法有如下五種。

  • router.get()
  • router.post()
  • router.put()
  • router.del()
  • router.patch()

這些動詞方法能夠接受兩個參數,第一個是路徑模式,第二個是對應的控制器方法(中間件),定義用戶請求該路徑時服務器行爲。

1 router.get('/', function *(next) {
2   this.body = 'Hello World!';
3 });

上面代碼中,router.get方法的第一個參數是根路徑,第二個參數是對應的函數方法。

注意,路徑匹配的時候,不會把查詢字符串考慮在內。好比,/index?param=xyz匹配路徑/index

有些路徑模式比較複雜,Koa-router容許爲路徑模式起別名。起名時,別名要添加爲動詞方法的第一個參數,這時動詞方法變成接受三個參數。

1 router.get('user', '/users/:id', function *(next) {
2  // ...
3 });
上面代碼中,路徑模式\users\:id的名字就是user。路徑的名稱,能夠用來引用對應的具體路徑,好比url方法能夠根據路徑名稱,
結合給定的參數,生成具體的路徑。

Koa-router容許爲路徑統一添加前綴。
1 var router = new Router({
2   prefix: '/users'
3 });
4 
5 router.get('/', ...); // 等同於"/users"
6 router.get('/:id', ...); // 等同於"/users/:id"

路徑的參數經過this.params屬性獲取,該屬性返回一個對象,全部路徑參數都是該對象的成員。

// 訪問 /programming/how-to-node
router.get('/:category/:title', function *(next) {
  console.log(this.params);
  // => { category: 'programming', title: 'how-to-node' }
});

param方法能夠針對命名參數,設置驗證條件。

 1 router
 2   .get('/users/:user', function *(next) {
 3     this.body = this.user;
 4   })
 5   .param('user', function *(id, next) {
 6     var users = [ '0號用戶', '1號用戶', '2號用戶'];
 7     this.user = users[id];
 8     if (!this.user) return this.status = 404;
 9     yield next;
10   })

上面代碼中,若是/users/:user的參數user對應的不是有效用戶(好比訪問/users/3),param方法註冊的中間件會查到,就會返回404錯誤。

redirect方法會將某個路徑的請求,重定向到另外一個路徑,並返回301狀態碼。

1 router.redirect('/login', 'sign-in');
2 
3 // 等同於
4 router.all('/login', function *() {
5   this.redirect('/sign-in');
6   this.status = 301;
7 });

redirect方法的第一個參數是請求來源,第二個參數是目的地,二者均可以用路徑模式的別名代替。

錯誤處理機制

1 app.use(function *() {
2   try {
3     yield saveResults();
4   } catch (err) {
5     this.throw(400, '數據無效');
6   }
7 });

上面代碼自行部署了try…catch代碼塊,一旦產生錯誤,就用this.throw方法拋出。該方法能夠將指定的狀態碼和錯誤信息,返回給客戶端。

對於未捕獲錯誤,能夠設置error事件的監聽函數。

1 app.on('error', function(err){
2   log.error('server error', err);
3 });

this.throw方法用於向客戶端拋出一個錯誤。

 1 this.throw(403);
 2 this.throw('name required', 400);
 3 this.throw(400, 'name required');
 4 this.throw('something exploded');
 5 
 6 this.throw('name required', 400)
 7 // 等同於
 8 var err = new Error('name required');
 9 err.status = 400;
10 throw err;

this.throw方法的兩個參數,一個是錯誤碼,另外一個是報錯信息。若是省略狀態碼,默認是500錯誤。

this.assert方法用於在中間件之中斷言,用法相似於Node的assert模塊

1 this.assert(this.user, 401, 'User not found. Please login!');

上面代碼中,若是this.user屬性不存在,會拋出一個401錯誤。

cookie的讀取和設置。

1 this.cookies.get('view');
2 this.cookies.set('view', n);

get和set方法均可以接受第三個參數,表示配置參數。其中的signed參數,用於指定cookie是否加密。若是指定加密的話,必須用app.keys指定加密短語。

1 app.keys = ['secret1', 'secret2'];
2 this.cookies.set('name', '張三', { signed: true });

this.cookie的配置對象的屬性以下。

  • signed:cookie是否加密。
  • expires:cookie什麼時候過時
  • path:cookie的路徑,默認是「/」。
  • domain:cookie的域名。
  • secure:cookie是否只有https請求下才發送。
  • httpOnly:是否只有服務器能夠取到cookie,默認爲true。

session

 1 var session = require('koa-session');
 2 var koa = require('koa');
 3 var app = koa();
 4 
 5 app.keys = ['some secret hurr'];
 6 app.use(session(app));
 7 
 8 app.use(function *(){
 9   var n = this.session.views || 0;
10   this.session.views = ++n;
11   this.body = n + ' views';
12 })
13 
14 app.listen(3000);
15 console.log('listening on port 3000');
 1 能夠把session存到mysql中
 2 安裝npm install koa-generic-session --save-dev
 3 2.app.js中
 4 var session = require('koa-generic-session');
 5 
 6 app.keys = ['my secret key'];  // needed for cookie-signing,設置一個簽名 Cookie 的密鑰
 7 app.use(session());
 8 
 9 3.
10 this.session.loginbean
11 
12 方法二:
13 session映射到mysql
14 1.加安裝
15 npm install mysql --save-dev
16 npm install koa-mysql-session --save-dev
17 
18 app.js中:
19 var session = require('koa-generic-session');
20 const mysql = require('mysql');
21 const MysqlStore = require('koa-mysql-session');
22 
23 app.keys = ['my secret key'];  // needed for cookie-signing,設置一個簽名 Cookie 的密鑰
24 app.use(session({store:new MysqlStore({
25   host: 'localhost',       //主機  
26   user: 'root',               //MySQL認證用戶名  
27   password: 'root',        //MySQL認證用戶密碼  
28   database: 'kameng',  
29   port: '3306',                   //端口號 
30   acquireTimeout:0
31 })}));

Request對象

Request對象表示HTTP請求。

(1)this.request.header

返回一個對象,包含全部HTTP請求的頭信息。它也能夠寫成this.request.headers

(2)this.request.method

返回HTTP請求的方法,該屬性可讀寫。

(3)this.request.length

返回HTTP請求的Content-Length屬性,取不到值,則返回undefined。

(4)this.request.path

返回HTTP請求的路徑,該屬性可讀寫。

(5)this.request.href

返回HTTP請求的完整路徑,包括協議、端口和url。

1 this.request.href
2 // http://example.com/foo/bar?q=1

(6)this.request.querystring

返回HTTP請求的查詢字符串,不含問號。該屬性可讀寫。

(7)this.request.search

返回HTTP請求的查詢字符串,含問號。該屬性可讀寫。

(8)this.request.host

返回HTTP請求的主機(含端口號)。

(9)this.request.hostname

返回HTTP的主機名(不含端口號)。

(10)this.request.type

返回HTTP請求的Content-Type屬性

1 var ct = this.request.type;
2 // "image/png"

(11)this.request.charset

返回HTTP請求的字符集。

1 this.request.charset
2 // "utf-8

路由

能夠經過this.path屬性,判斷用戶請求的路徑,從而起到路由做用。

app.use(function* (next) { if (this.path === '/') { this.body = 'we are at home!'; } else { yield next; } }) // 等同於 app.use(function* (next) { if (this.path !== '/') return yield next; this.body = 'we are at home!'; }) 

下面是多路徑的例子。

let koa = require('koa') let app = koa() // normal route app.use(function* (next) { if (this.path !== '/') { return yield next } this.body = 'hello world' }); // /404 route app.use(function* (next) { if (this.path !== '/404') { return yield next; } this.body = 'page not found' }); // /500 route app.use(function* (next) { if (this.path !== '/500') { return yield next; } this.body = 'internal server error' }); app.listen(8080) 

上面代碼中,每個中間件負責一個路徑,若是路徑不符合,就傳遞給下一個中間件。

複雜的路由須要安裝koa-router插件。

var app = require('koa')(); var Router = require('koa-router'); var myRouter = new Router(); myRouter.get('/', function *(next) { this.response.body = 'Hello World!'; }); app.use(myRouter.routes()); app.listen(3000); 

上面代碼對根路徑設置路由。

Koa-router實例提供一系列動詞方法,即一種HTTP動詞對應一種方法。典型的動詞方法有如下五種。

  • router.get()
  • router.post()
  • router.put()
  • router.del()
  • router.patch()

這些動詞方法能夠接受兩個參數,第一個是路徑模式,第二個是對應的控制器方法(中間件),定義用戶請求該路徑時服務器行爲。

router.get('/', function *(next) { this.body = 'Hello World!'; }); 

上面代碼中,router.get方法的第一個參數是根路徑,第二個參數是對應的函數方法。

注意,路徑匹配的時候,不會把查詢字符串考慮在內。好比,/index?param=xyz匹配路徑/index

有些路徑模式比較複雜,Koa-router容許爲路徑模式起別名。起名時,別名要添加爲動詞方法的第一個參數,這時動詞方法變成接受三個參數。

router.get('user', '/users/:id', function *(next) { // ... }); 

上面代碼中,路徑模式\users\:id的名字就是user。路徑的名稱,能夠用來引用對應的具體路徑,好比url方法能夠根據路徑名稱,結合給定的參數,生成具體的路徑。

router.url('user', 3); // => "/users/3" router.url('user', { id: 3 }); // => "/users/3" 

上面代碼中,user就是路徑模式的名稱,對應具體路徑/users/:id。url方法的第二個參數3,表示給定id的值是3,所以最後生成的路徑是/users/3

Koa-router容許爲路徑統一添加前綴。

var router = new Router({ prefix: '/users' }); router.get('/', ...); // 等同於"/users" router.get('/:id', ...); // 等同於"/users/:id" 

路徑的參數經過this.params屬性獲取,該屬性返回一個對象,全部路徑參數都是該對象的成員。

// 訪問 /programming/how-to-node router.get('/:category/:title', function *(next) { console.log(this.params); // => { category: 'programming', title: 'how-to-node' } }); 

param方法能夠針對命名參數,設置驗證條件。

router .get('/users/:user', function *(next) { this.body = this.user; }) .param('user', function *(id, next) { var users = [ '0號用戶', '1號用戶', '2號用戶']; this.user = users[id]; if (!this.user) return this.status = 404; yield next; }) 

上面代碼中,若是/users/:user的參數user對應的不是有效用戶(好比訪問/users/3),param方法註冊的中間件會查到,就會返回404錯誤。

redirect方法會將某個路徑的請求,重定向到另外一個路徑,並返回301狀態碼。

router.redirect('/login', 'sign-in'); // 等同於 router.all('/login', function *() { this.redirect('/sign-in'); this.status = 301; }); 

redirect方法的第一個參數是請求來源,第二個參數是目的地,二者均可以用路徑模式的別名代替。

context對象

中間件當中的this表示上下文對象context,表明一次HTTP請求和迴應,即一次訪問/迴應的全部信息,均可以從上下文對象得到。context對象封裝了request和response對象,而且提供了一些輔助方法。每次HTTP請求,就會建立一個新的context對象。

app.use(function *(){ this; // is the Context this.request; // is a koa Request this.response; // is a koa Response }); 

context對象的不少方法,實際上是定義在ctx.request對象或ctx.response對象上面,好比,ctx.type和ctx.length對應於ctx.response.type和ctx.response.length,ctx.path和ctx.method對應於ctx.request.path和ctx.request.method。

context對象的全局屬性。

  • request:指向Request對象
  • response:指向Response對象
  • req:指向Node的request對象
  • res:指向Node的response對象
  • app:指向App對象
  • state:用於在中間件傳遞信息。
this.state.user = yield User.find(id); 

上面代碼中,user屬性存放在this.state對象上面,能夠被另外一箇中間件讀取。

context對象的全局方法。

  • throw():拋出錯誤,直接決定了HTTP迴應的狀態碼。
  • assert():若是一個表達式爲false,則拋出一個錯誤。
this.throw(403); this.throw('name required', 400); this.throw('something exploded'); this.throw(400, 'name required'); // 等同於 var err = new Error('name required'); err.status = 400; throw err; 

assert方法的例子。

// 格式 ctx.assert(value, [msg], [status], [properties]) // 例子 this.assert(this.user, 401, 'User not found. Please login!'); 

如下模塊解析POST請求的數據。

  • co-body
  • https://github.com/koajs/body-parser
  • https://github.com/koajs/body-parsers
var parse = require('co-body'); // in Koa handler var body = yield parse(this); 

錯誤處理機制

Koa提供內置的錯誤處理機制,任何中間件拋出的錯誤都會被捕捉到,引起向客戶端返回一個500錯誤,而不會致使進程中止,所以也就不須要forever這樣的模塊重啓進程。

app.use(function *() { throw new Error(); }); 

上面代碼中,中間件內部拋出一個錯誤,並不會致使Koa應用掛掉。Koa內置的錯誤處理機制,會捕捉到這個錯誤。

固然,也能夠額外部署本身的錯誤處理機制。

app.use(function *() { try { yield saveResults(); } catch (err) { this.throw(400, '數據無效'); } }); 

上面代碼自行部署了try…catch代碼塊,一旦產生錯誤,就用this.throw方法拋出。該方法能夠將指定的狀態碼和錯誤信息,返回給客戶端。

對於未捕獲錯誤,能夠設置error事件的監聽函數。

app.on('error', function(err){ log.error('server error', err); }); 

error事件的監聽函數還能夠接受上下文對象,做爲第二個參數。

app.on('error', function(err, ctx){ log.error('server error', err, ctx); }); 

若是一個錯誤沒有被捕獲,koa會向客戶端返回一個500錯誤「Internal Server Error」。

this.throw方法用於向客戶端拋出一個錯誤。

this.throw(403); this.throw('name required', 400); this.throw(400, 'name required'); this.throw('something exploded'); this.throw('name required', 400) // 等同於 var err = new Error('name required'); err.status = 400; throw err; 

this.throw方法的兩個參數,一個是錯誤碼,另外一個是報錯信息。若是省略狀態碼,默認是500錯誤。

this.assert方法用於在中間件之中斷言,用法相似於Node的assert模塊。

this.assert(this.user, 401, 'User not found. Please login!'); 

上面代碼中,若是this.user屬性不存在,會拋出一個401錯誤。

因爲中間件是層級式調用,因此能夠把try { yield next }當成第一個中間件。

app.use(function *(next) { try { yield next; } catch (err) { this.status = err.status || 500; this.body = err.message; this.app.emit('error', err, this); } }); app.use(function *(next) { throw new Error('some error'); })

CSRF攻擊

CSRF攻擊是指用戶的session被劫持,用來冒充用戶的攻擊。

koa-csrf插件用來防止CSRF攻擊。原理是在session之中寫入一個祕密的token,用戶每次使用POST方法提交數據的時候,必須含有這個token,不然就會拋出錯誤。

 1 var koa = require('koa');
 2 var session = require('koa-session');
 3 var csrf = require('koa-csrf');
 4 var route = require('koa-route');
 5 
 6 var app = module.exports = koa();
 7 
 8 app.keys = ['session key', 'csrf example'];
 9 app.use(session(app));
10 
11 app.use(csrf());
12 
13 app.use(route.get('/token', token));
14 app.use(route.post('/post', post));
15 
16 function* token () {
17   this.body = this.csrf;
18 }
19 
20 function* post() {
21   this.body = {ok: true};
22 }
23 
24 app.listen(3000);

POST請求含有token,能夠是如下幾種方式之一,koa-csrf插件就能得到token。

  • 表單的_csrf字段
  • 查詢字符串的_csrf字段
  • HTTP請求頭信息的x-csrf-token字段
  • HTTP請求頭信息的x-xsrf-token字段
相關文章
相關標籤/搜索