里面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
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
,即 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。
说实话没试过这种方式,有点不习惯。
Koa Context 将 Nodejs 的 request
和 response
对象封装到单个对象中,每一个请求都将建立一个 Context
,并在中间件中做为接收器引用,或者 ctx
标识符,许多上下文的访问器和方法直接委托给它们的 ctx.request
或 ctx.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 查看效果)
Koa Request 对象是在 Nodejs 的 vanilla
请求对象之上的抽象,提供了诸多对 HTTP 服务器开发有用的功能。Koa的 Request 对象包括由 accepts
和 negotiator
提供的有用的内容协商实体。
由于用法都一个样,挑选一个来说解。
检查给定的类型是否能够接受,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/403
和 http://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内容。