Koa是基于Node.js的下一代web框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。
特点是优雅、简洁、表达力强、自由度高
1.创建项目安装依赖
检查Node版本,Koa 必须使用 7.6 以上的版本
mkdir learn_koa cd learn_koa npm init -y
npm install koa
2.基本使用
(1)架设http服务
创建app.js
const Koa = require(\'koa\'); const app = new Koa(); app.listen(3001);
启动
浏览器查看,http://localhost:3001/
页面显示Not Found ,因为没告诉它要显示什么,只是启动了服务
(2)Context 对象
Koa将Node的request 和 response对象都封装到了context中,每次请求都会创建一个ctx,并且在中间件中作为接收器使用
页面输出 Hello World
const Koa = require(\'koa\');
const app = new Koa();
app.use(async ctx => {
ctx.body = \'Hello World\';
});
app.listen(3001,function(){
console.log(\'Example app listening on port 3001!\');
});
启动
node app.js
查看,浏览器打开http://localhost:3001/
(3)请求(Request)和响应(Response)
默认的返回类型是text/plain
先用ctx.request.accepts判断一下,然后使用ctx.response.type指定返回类型
const Koa = require(\'koa\'); const app = new Koa(); //设置跨域访问 app.use(async (ctx,next) =>{ ctx.set(\'Access-Control-Allow-Origin\', \'*\'); ctx.set(\'Access-Control-Allow-Headers\', \'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild\'); ctx.set(\'Access-Control-Allow-Methods\', \'PUT, POST, GET, DELETE, OPTIONS\'); console.log(ctx.accepts(\'json\', \'html\', \'text\',\'xml\')); switch (ctx.accepts(\'json\', \'html\', \'text\',\'xml\')) { case \'json\': ctx.response.type = \'json\'; ctx.response.body = { type:\'json\',data: \'Hello World\' }; break; case \'html\': ctx.response.type = \'html\'; ctx.response.body = \'html:\'+\'<p>Hello World</p>\'; break; case \'text\': ctx.response.type = \'text\'; ctx.response.body = \'text:\'+\'Hello World\'; break; case \'xml\': ctx.response.type = \'xml\'; ctx.response.body = \'<?xml version="1.0" encoding="UTF-8"?><root><type>xml</type><tag>Hello World</tag></root>\'; break; default: ctx.throw(406, \'json, html,xml, or text only\'); } }); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
test.html
<!DOCTYPE html> <!-- saved from url=(0059)http://www.17sucai.com/preview/11/2017-08-29/sjz/index.html --> <html lang="zh"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>测试</title> <script src="static/jquery.js"></script> </head> <body> <script> $.ajax({ url:"http://localhost:3001/", contentType:\'text/html;charset=utf-8\', dataType:\'xml\', success:function(result) { console.log(result); } }); </script> </body> </html>
dataType:\'xml\'
dataType:\'json\'
dataType:\'text\'
(4)网页模板
让 Koa 先读取模板文件,然后将这个模板返回给用户
const fs = require(\'fs\'); const Koa = require(\'koa\'); const app = new Koa(); const main = ctx => { ctx.response.type = \'html\'; ctx.response.body = fs.createReadStream(\'./tpl/tpl1.html\'); }; app.use(main); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
tpl/tpl1.html
<!DOCTYPE html>
<html lang="zh"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>测试</title>
</head>
<body>
<h2>hello world</h2>
</body>
</html>
结果
3.路由
(1)原生路由
通过ctx.request.path可以获取用户请求的路径
const Koa = require(\'koa\'); const app = new Koa(); app.use(async (ctx,next) =>{ if (ctx.request.path !== \'/\') { ctx.response.type = \'html\'; ctx.response.body = \'<a href="/">Index</a>\'; } else { ctx.response.body = \'Hello World\'; } }); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
http://localhost:3001/aa
(2)koa-route 模块
安装
npm install koa-route
根路径/的处理函数是main,/about路径的处理函数是about
const Koa = require(\'koa\'); const route = require(\'koa-route\'); const app = new Koa(); const about = ctx => { ctx.response.type = \'html\'; ctx.response.body = \'<a href="/">Index</a>\'; }; const main = ctx => { ctx.response.body = \'Hello World\'; }; app.use(route.get(\'/\', main)); app.use(route.get(\'/about\', about)); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
http://localhost:3001/about
(3)静态资源,koa-static模块
获取静态资源(图片、字体、样式表、脚本......)
安装
npm install koa-static
const Koa = require(\'koa\'); const app = new Koa(); const path = require(\'path\'); const serve = require(\'koa-static\'); const main = serve(path.join(__dirname)); app.use(main); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
http://localhost:3001/timg.jpg
(4)重定向
ctx.response.redirect()方法
const Koa = require(\'koa\'); const route = require(\'koa-route\'); const app = new Koa(); const redirect = ctx => { ctx.response.redirect(\'/\'); }; const main = ctx => { ctx.response.body = \'Hello World\'; }; app.use(route.get(\'/\', main)); app.use(route.get(\'/redirect\', redirect)); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
http://localhost:3001//redirect ,浏览器会跳转到首页
4.中间件
Koa最大的特色和最优的设计就是中间件(middleware),在匹配路由之前和匹配路由之后执行函数。
使用app.use()加载中间件。
每个中间件接收两个参数,ctx对象和next函数,通过调用next将执行权交给下一个中间件。
eg:Logger 功能
const Koa = require(\'koa\'); const app = new Koa(); const logger = (ctx, next) => { console.log(`${new Date().toLocaleString()} ${ctx.request.method} ${ctx.request.url}`); next(); } const main = ctx => { ctx.response.body = \'Hello World\'; }; app.use(logger); app.use(main); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
每次访问都会在命令窗口输出
(1)中间件分为:
A.应用级中间件
任何路由都会先经过应用级中间件,当执行完成next后再去匹配相应的路由
const Koa = require(\'koa\'); const Router = require(\'koa-router\'); const app = new Koa(); const router = new Router(); // 应用级中间件 app.use(async (ctx, next) => { await next(); }) router.get(\'/\', async ctx => { ctx.body = \'hello world\'; }) // 启动路由 app.use(router.routes()).use(router.allowedMethods()); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
B.路由级中间件
路由匹配过程中,对于相同路由会从上往下依次执行中间件,直到最后一个没有next参数的中间件为止。
const Koa = require(\'koa\'); const Router = require(\'koa-router\'); const app = new Koa(); const router = new Router(); router.get(\'/user\', async (ctx, next) => { console.log(111) await next(); }) router.get(\'/user\', async (ctx, next) => { console.log(222) await next(); }) router.get(\'/user\', async ctx => { console.log(333) ctx.body = \'Hello\' }) // 启动路由 app.use(router.routes()).use(router.allowedMethods()); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
C.错误处理中间件
路由在匹配成功并执行完相应的操作后还会再次进入应用级中间件执行 next 之后的逻辑。
所以对于404、500等错误可以在最外层的(第一个)应用级中间件的next之后做相应的处理。
const Koa = require(\'koa\'); const Router = require(\'koa-router\'); const app = new Koa(); const router = new Router(); router.get(\'/user\', async (ctx, next) => { console.log(111) await next(); }) router.get(\'/\', async ctx => { ctx.body = \'hello world\'; }) app.use(async (ctx, next)=> { await next(); if(ctx.status === 404){ ctx.body="404页面" } }); // 启动路由 app.use(router.routes()).use(router.allowedMethods()); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
D.第三方中间件,如koa-router、koa-bodyparser等就是第三方中间件
koa-compose模块可以将多个中间件合成为一个const Koa = require(\'koa\'); const app = new Koa(); const compose = require(\'koa-compose\'); const logger = (ctx, next) => { console.log(`${new Date().toLocaleString()} ${ctx.request.method} ${ctx.request.url}`); next(); } const main = ctx => { ctx.response.body = \'Hello World\'; }; const middlewares = compose([logger, main]); app.use(middlewares); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
(3)中间件的执行顺序
多个中间件会形成一个栈结构(middle stack),以"先进后出"(first-in-last-out)的顺序执行
A.最外层的中间件首先执行。
B.调用next函数,把执行权交给下一个中间件。
C....
D.最内层的中间件最后执行。
E.执行结束后,把执行权交回上一层的中间件。
F....
G.最外层的中间件收回执行权之后,执行next函数后面的代码。
5.错误处理
Koa 提供了ctx.throw()方法
(1)500
如果代码运行过程中发生错误,要返回500状态码。
用ctx.throw(500)就是抛出500错误
const Koa = require(\'koa\'); const app = new Koa(); const main = ctx => { ctx.throw(500); }; app.use(main); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
(2)404
ctx.throw(404),返回404错误
const Koa = require(\'koa\'); const app = new Koa(); const main = ctx => { ctx.response.status = 404; ctx.response.body = \'Page Not Found\'; }; app.use(main); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
(3)处理错误的中间件try...catch
让最外层的中间件,负责所有中间件的错误处理
const Koa = require(\'koa\'); const app = new Koa(); const handler = async (ctx, next) => { try { await next(); } catch (err) { ctx.response.status = err.statusCode || err.status || 500; ctx.response.body = { message: err.message }; } }; const main = ctx => { ctx.throw(500); }; app.use(handler); app.use(main); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
(5)error 事件的监听
运行过程中一旦出错,Koa 会触发一个error事件
监听error事件,处理错误
const Koa = require(\'koa\'); const app = new Koa(); const main = ctx => { ctx.throw(500); }; app.on(\'error\', (err, ctx) => { console.error(\'server error\', err); }); app.use(main); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
(6)释放 error 事件
如果错误被try...catch捕获,就不会触发error事件。
必须调用ctx.app.emit(),手动释放error事件,才能让监听函数生效
const Koa = require(\'koa\'); const app = new Koa(); const handler = async (ctx, next) => { try { await next(); } catch (err) { ctx.response.status = err.statusCode || err.status || 500; ctx.response.type = \'html\'; ctx.response.body = \'<p>Something wrong, please contact administrator.</p>\'; ctx.app.emit(\'error\', err, ctx); } }; const main = ctx => { ctx.throw(500); }; app.on(\'error\', function(err) { console.log(\'logging error \', err.message); console.log(err); }); app.use(handler); app.use(main); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
main函数抛出错误,被handler函数捕获。catch代码块里面使用ctx.app.emit()手动释放error事件,监听函数才能监听到
6.web app
(1)获取请求数据
A.GET
GET传值通过request接收,有两种方式
query:返回的是参数对象。 {name: \'baby\', age: 123}
querystring:返回的是请求字符串。 name=baby&age=123
query和querystring可以从request中获取,也可以直接从ctx中获取
const Koa = require(\'koa\'); const app = new Koa(); const logger = (ctx, next) => { console.log(`${new Date().toLocaleString()} ${ctx.request.method} ${ctx.request.url}`); next(); } const main = ctx => { let name = ctx.request.query.name; let age = ctx.request.query.age ctx.response.body = \'Hello \'+name+",age "+age; }; app.use(logger); app.use(main); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
B.POST
通过post传递的值可以通过
原生Node封装
const Koa = require(\'koa\'); const app = new Koa(); const querystring = require(\'querystring\'); app.use(async(ctx) => { let pastData=await parsePostData(ctx); console.log(pastData); ctx.body=\'Hello \'+pastData.name+",age "+pastData.age; function parsePostData( ctx ) { return new Promise((resolve, reject) => { try { let postdata = ""; ctx.req.addListener(\'data\', (data) => { postdata += data }) ctx.req.addListener("end",function(){ let parseData = parseQueryStr( postdata ) resolve( parseData ) }) } catch ( err ) { reject(err) } }) }; // 处理 string => json function parseQueryStr(queryStr) { let queryData = {}; let queryStrList = queryStr.split(\'&\'); console.log(\'queryStrList\',queryStrList); console.log(\'queryStrList.entries()\',queryStrList.entries()); for(let [index,queryStr] of queryStrList.entries()){ let itemList = queryStr.split(\'=\'); console.log(\'itemList\',itemList); queryData[itemList[0]] = decodeURIComponent(itemList[1]); } return queryData; } }); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
第三方模块接收,如使用koa-bodyparser模块
安装
npm install koa-bodyparser
const Koa = require(\'koa\'); const app = new Koa(); const bodyParser = require(\'koa-bodyparser\') app.use(bodyParser()); app.use(async(ctx) => { let pastData=ctx.request.body; ctx.body=\'Hello \'+pastData.name+",age "+pastData.age; }); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
(2)cookie
cookie 是存放在客户端
cookie是当第一次访问服务器的时候,服务器在下行HTTP报文时给浏览器分配一个具有特殊标识的字段,此后当浏览器再次访问同一域名的,将该字段t通过请求头携带到服务器
获取ctx.cookies.get(name, [options])
设置ctx.cookies.set(name, value, [options])
const Koa = require(\'koa\'); const app = new Koa(); const main = function(ctx) { const n = Number(ctx.cookies.get(\'view\') || 0) + 1; ctx.cookies.set(\'view\', n); ctx.response.body = n + \' views\'; } app.use(main); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
每刷新一次页面,都会加1
(3)session
session是存储在服务端的
当浏览器第一次请求服务器时,服务器端会创建一个session对象,生成一个类似于key-value的键值对, 然后将key(cookie)下发到浏览器(客户)端,浏览器再访问时,携带key(cookie),找到对应的session(value)
安装
npm install koa-session
使用
const Koa = require(\'koa\'); const app = new Koa(); const session = require(\'koa-session\'); app.keys = [\'some secret hurr\']; const CONFIG = { key: \'koa:sess\', /** (string) cookie key (default is koa:sess) */ maxAge: 86400000, overwrite: true, /** (boolean) can overwrite or not (default true) */ httpOnly: true, /** (boolean) httpOnly or not (default true) */ signed: true, /** (boolean) signed or not (default true) */ rolling: false, /** (boolean) Force a session identifier cookie to be set on every response. The expiration is reset to the original maxAge, resetting the expiration countdown. (default is false) */ renew: false, /** (boolean) renew session when session is nearly expired, so we can always keep user logged in. (default is false)*/ }; app.use(session(CONFIG, app)); app.use(ctx => { // ignore favicon if (ctx.path === \'/favicon.ico\') return; let n = ctx.session.views || 0; ctx.session.views = ++n; ctx.body = n + \' views\'; }); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
session中保存了页面访问次数,每次请求的时候,会增加计数再把结果返回给用户
koa-session同时支持cookie和外部存储
默认使用cookie来存储session信息,如图
cookie的内容类似 koa:sess=eyJ2aWV3cyI6NiwiX2V4cGlyZSI6MTU2MzM0NzUyMDg5NiwiX21heEFnZSI6ODY0MDAwMDB9
koa的cookie本身带了安全机制,也就是config里的signed设为true的时候,会自动给cookie加上一个sha256的签名,类似 koa:sess.sig=SZLO7qMX9eZmuhB9Xm9GY52Nghg,从而防止cookie被篡改
(4)文件上传
koa-body模块还可以用来处理文件上传
koa-body通过ctx.request.files获取上传的文件
const os = require(\'os\'); const path = require(\'path\'); const Koa = require(\'koa\'); const fs = require(\'fs\'); const koaBody = require(\'koa-body\'); const app = new Koa(); app.use(koaBody({multipart:true})); app.use(ctx => { // 获取文件 let file = ctx.request.files.files; const reader = fs.createReadStream(file.path); let filePath = path.join(__dirname, \'upload/\') + `/${file.name}`; // 创建可写流 const upStream = fs.createWriteStream(filePath); // 可读流通过管道写入可写流 reader.pipe(upStream); ctx.body = { url: filePath, message: "文件上传成功", cc: 0 } }); app.listen(3001,function(){ console.log(\'Example app listening on port 3001!\'); });
对应目录下也上传了文件
说明:
async是声明一个方法是异步的,await是等待异步方法完成
值得注意的是await必须在async方法中才可以使用因为await访问本身就会造成程序停止堵塞,所以必须在异步方法中才可以使用
解决跨域,GET请求直接返回200
app.use(async (ctx,next) =>{
ctx.set(\'Access-Control-Allow-Origin\', \'*\');
ctx.set(\'Access-Control-Allow-Headers\', \'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild\');
ctx.set(\'Access-Control-Allow-Methods\', \'PUT, POST, GET, DELETE, OPTIONS\');
if (ctx.method == \'GET\') {
ctx.body = 200;
} else {
await next();
}
});