Koa1 框架
安装创建项目:
1.一定要全局安装(koa1.2和koa2都己经支持)
npm install koa-generator -g
2.
koa1 生成一个test项目,切到test目录并下载依赖
koa1创建项目
koa test
cd test
npm install
运行:npm start
访问:http://localhost:3000
Koa是一个类似于Express的Web开发框架,创始人也是同一个人。它的主要特点是,使用了ES6的Generator函数,进行了架构的重新设计。也就是说,Koa的原理和内部结构很像Express,但是语法和内部结构进行了升级。
官方faq有这样一个问题:”为什么koa不是Express 4.0?“,回答是这样的:”Koa与Express有很大差异,整个设计都是不同的,所以如果将Express 3.0按照这种写法升级到4.0,就意味着重写整个程序。所以,我们觉得创造一个新的库,是更合适的做法。“
Koa应用
一个Koa应用就是一个对象,包含了一个middleware数组,这个数组由一组Generator函数组成。这些函数负责对HTTP请求进行各种加工,比如生成缓存、指定代理、请求重定向等等。
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的网页。
app.use方法用于向middleware数组添加Generator函数。
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
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字段