# koa-server **Repository Path**: topazur/koa-server ## Basic Information - **Project Name**: koa-server - **Description**: 方便自己使用,感兴趣的也可以来看看! - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-05-20 - **Last Updated**: 2020-12-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Koa (koajs) -- 基于 Node.js 平台的下一代 web 开发框架 | Koajs 中文文档 > 与Express差不多(没有配置内置路由),但是避免了异步的嵌套。阿里在koa的基础上封装了egg # 安装 ## Node.js版本必须高于V7.6,因为在V7.6才完全支持async/await语法; 要在 node < 7.6 版本的 Koa 中使用 async 方法, 我们推荐使用 Babel 实现 Async 方法,使用 [babel's require hook](https://www.babeljs.cn/docs/babel-register). ## 安装没有脚手架 npm init -y npm install koa --save ## 有脚手架 ```js 脚手架根据: npm i koa-generator -g 生成ejs模板: koa2 -e your-projectName(生成版本2,-e表示ejs模板) ``` ### 启动脚本 ```json `node --debug-brk=40645 --nolazy app.js` - 调试?? `node app.js` - 执行js文件 `npm start` - 脚本 // 自启动工具 "watch": "nodemon bin/www", "dev": "supervisor bin/www", "docs": "apidoc -i routes/ -o static/doc", ``` ```bash "dependencies": { "koa": "^2.12.0", "koa-bodyparser": "^4.3.0", "koa-router": "^8.0.8", "mime": "^2.4.5", "mz": "^2.7.0", "nunjucks": "^3.2.1" } ``` ### app.js ```js // express写法 app.use(function(req,res) { // more res.send('express的嵌套写法') }) // koa写法 app.use(async (ctx, next) => { await next(); ctx.response.body = "koa的async/await写法" }) ``` #### 设置 1. 级联的概念: 其实如果路由返回之后不往下走了是不用写next的; next的表示只在此做一层逻辑,然后继续往下走 2. 我们知道Koa是一个构造函数,那么我们需要关注以下几点 ```js // 一、Koa实例app上的属性: /* 1. app.env `默认是 NODE_ENV 或 "development"` 2. app.keys `签名的 cookie 密钥数组` 3. app.proxy `当真正的代理头字段将被信任时` 4. `忽略 .subdomains 的 app.subdomainOffset 偏移量,默认为 2` 5. app.proxyIpHeader `代理 ip 消息头, 默认为 X-Forwarded-For` 6. app.maxIpsCount `从代理 ip 消息头读取的最大 ips, 默认为 0 (代表无限)` */ 方式一: 通过参数传递给构造函数 const app = new Koa({ proxy: true }); 方式二: 动态的设置 app.proxy = true; // 二、Koa实例app上的方法: app.listen(3000, cb); app.callback() app.use(function) app.keys=[] //设置签名的 Cookie 密钥 app.context //app.context 是从其创建 ctx 的原型,添加全局使用的属性,如: 设置app.context.db = db();=>就可以通过ctx.db访问 // 错误处理 app.on('error', (err, ctx) => { // Koa 将用一个 500 “内部服务器错误” 进行适当的响应,进行客户端的优化处理 console.error('server error', err) }); // 上下文 app.use(async ctx => { ctx; // 这是 Context ctx.request; // 这是 koa Request ctx.response; // 这是 koa Response // ctx.type 和 ctx.length 委托给 response 对象,ctx.path 和 ctx.method 委托给 request ctx.req; || ctx.request; //Node 的 request 对象. ctx.res; || ctx.response; //Node 的 response 对象. /* 绕过 Koa 的 response 处理是 不被支持的. 应避免使用以下 node 属性 res.statusCode res.writeHead() res.write() res.end()*/ ctx.state; //命名空间 ctx.app; //应用程序实例引用 ctx.app.emit; //扩展的内部的EventEmitter ctx.cookies.get(name, [options]);ctx.cookies.set(name, value, [options]); ctx.throw([status], [msg], [properties]); //抛出一个包含 .status 属性错误的帮助方法,其默认值为 500(原理: koa 使用 http-errors 来创建错误) ctx.assert(value, [status], [msg], [properties]); //当 !value 时抛出一个类似 .throw 错误的帮助方法(原理: koa 使用 http-assert 作为断言) ctx.respond; //为了绕过 Koa 的内置 response 处理,想要写入原始的 res 对象而不是让 Koa 处理你的 response.(Koa 中使用传统的 fn(req, res) 功能和中间件的人,而不建议在koa的async中使用,否则被认为是一个 hack) }); ``` 3. request Request 别名 | | | | 以下访问器和 Request.xxx 别名等效 :-: | :-: | :-: | :-: | :-: ctx.header | ctx.headers | ctx.method | ctx.method= | **ctx.socket**| ctx.url | ctx.url=| ctx.originalUrl | ctx.origin | ctx.href| ctx.path | ctx.path=| ctx.query | ctx.query= | ctx.querystring| ctx.querystring= | ctx.host| ctx.hostname | ctx.fresh | ctx.stale| ctx.protocol | ctx.secure| ctx.ip | ctx.ips | ctx.subdomains| ctx.is() | ctx.accepts()| ctx.acceptsEncodings() | ctx.acceptsCharsets() |空 | ctx.acceptsLanguages() | ctx.get()| 空|空 | 空| 4. response Response 别名 | | | | 以下访问器和 Response.xxx 别名等效 :-: | :-: | :-: | :-: | :-: **ctx..status&ctx.message**| ctx.length| **ctx.body** | | | ctx.get(field)不区分大小写 | ctx.has(field)| ctx.set(field, value) | ctx.append(field, value) | ctx.set(fields)| ctx.remove(field) | ctx.type| ctx.redirect(url, [alt]) | ctx.attachment([filename], [options]) | ctx.headerSent| ctx.lastModified | ctx.etag| ctx.vary(field) | ctx.flushHeaders() | ...| 3.2 get传值 如何获取参数? 方式一:(推荐) ctx.query //打印{aid: '123'} 获取的是对象(用的最多的方式) ctx.querystring // aid=123&name=zhangsan 获取的是一个字符串 方式二:我们可以从ctx里面的request里面获取get传值 ctx.request.requery // {aid: '123'} ctx.quest.querystring // aid=123&name=zhangsan 3.3 动态路由 router.get('/newscontent/:aid', async (ctx) => { console.log(ctx.params) // { aid : '123'} ctx.body = '新闻详情' }) 动态路由可以传入多个值 router.get('/newscontent/:aid/:cid', async (ctx) => { console.log(ctx.params) //{aid:'123' , cid: '456'} ctx.body = '新闻详情' }) 3.3 post应用插件之后: ctx.request.body ### 中间件 koa middleware 如果get、post回调函数中没有next参数,name就匹配上第一个路由,就不会往下匹配了,如果想往下匹配,那么就需要写next() 4.1 中间件使用 中间件的功能:执行任何代码、修改请求和响应对象、 终结请求--响应循环、调用堆栈中的下一个中间件 4.2 应用级中间件 定义:就是在匹配所有路由之前进行操作某种需求 第一个参数没有,或者是共有的''和'/' => 当前路由匹配完成以后继续向下匹配,不写就不会向下匹配 4.3 路由中间件 定义: 匹配特定路由前进行某种操作 4.4 错误处理中间件 express的中间件和路由有前后顺序,而koa没有顺序,即使放在路由最后面,也先执行中间件 4.5 执行原理: koa就像洋葱一样,先执行request,匹配到之后,然后再反向执行response(从外到内,再从内到外) 让我们再仔细看看koa的执行逻辑。核心代码是: ```js app.use(async (ctx, next) => { await next(); ctx.response.type = 'text/html'; ctx.response.body = '

Hello, koa2!

'; }); ``` 每收到一个http请求,koa就会调用通过app.use()注册的async函数,并传入ctx和next参数。 我们可以对ctx操作,并设置返回内容。但是为什么要调用await next()? 原因是koa把很多async函数组成一个处理链,每个async函数都可以做一些自己的事情,然后用await next()来调用下一个async函数。我们把每个async函数称为middleware,这些middleware可以组合起来,完成很多有用的功能。 例如,可以用以下3个middleware组成处理链,依次打印日志,记录处理时间,输出HTML: ```js app.use(async (ctx, next) => { console.log(`${ctx.request.method} ${ctx.request.url}`); // 打印URL await next(); // 调用下一个middleware }); app.use(async (ctx, next) => { const start = new Date().getTime(); // 当前时间 await next(); // 调用下一个middleware const ms = new Date().getTime() - start; // 耗费时间 console.log(`Time: ${ms}ms`); // 打印耗费时间 }); app.use(async (ctx, next) => { await next(); ctx.response.type = 'text/html'; ctx.response.body = '

Hello, koa2!

'; }); ``` middleware的顺序很重要,也就是调用app.use()的顺序决定了middleware的顺序。 此外,如果一个middleware没有调用await next(),会怎么办?答案是后续的middleware将不再执行了。这种情况也很常见,例如,一个检测用户权限的middleware可以决定是否继续处理请求,还是直接返回403错误: ```js app.use(async (ctx, next) => { if (await checkUserPermission(ctx)) { await next(); } else { ctx.response.status = 403; } }); ``` 理解了middleware,我们就已经会用koa了! 最后注意ctx对象有一些简写的方法,例如ctx.url相当于ctx.request.url,ctx.type相当于ctx.response.type。 ### url koa-router ### 模板引擎 Nunjucks 局限: JavaScript的模板字符串必须写在JavaScript代码中 转义: 对特殊字符要转义,避免受到XSS攻击。比如,如果变量name的值不是小明,而是小明,模板引擎输出的HTML到了浏览器,就会自动执行恶意JavaScript代码。 格式化: 不同数据的处理方式不同 条件编译: 执行一些简单的逻辑 => `{{ name }}` 、 `{% if score >= 90 %}` > Nunjucks(用JavaScript重新实现了Python的模板引擎jinjia2)是Mozilla开发的一个纯JavaScript编写的模板引擎,既可以用在Node环境下,又可以运行在浏览器端。但是,主要还是运行在Node环境下,因为浏览器端有更好的模板解决方案,例如MVVM框架。 > 继承、条件编译、循环 本质:function render(view, model) { return `....`} 通过视图(模板名)和数据返回一个字符串 [Nunjucks](http://mozilla.github.io/nunjucks/)、[模板引擎-廖雪峰](https://www.liaoxuefeng.com/wiki/1022910821149312/1100400176397024) MVC: koa2、Nunjucks整合起来,然后,把原来直接输出字符串的方式,改为ctx.render(view, model)的方式 模板: 变量模板: <%= title %> 可以直接使用ctx.state中的变量-是一个全局的公共状态<例如: ctx.state.title> 逻辑模板: <% for(var i=0; i 解析标签模板: <%- include('public/header.ejs') -%> ## [在线的MongoDB数据库](https://mlab.com/) 500M免费的数据库 - mlab现在是MongoDB的一部分(自2019年2月以来,mLab已禁止创建新帐户;而是去MongoDB登录) 注册 - 谷歌邮箱 创建 - DataBase 2020.05.19-成果见readme 连接mlab线上数据库 响应体的返回设置及接口async/await的写法 session和cookie设置 Schema的理解 组合查找的方法 全球公认头像 jwt验证的两种方式: 一、koa-passport(注册中间件及通过passport.authenticate('jwt', { session: false })把接口变为私密-即需要验证的接口) && passport-jwt(通过请求头中的**Authorization**验证token,并通过回调做不同处理) 特点: 虽然配置麻烦(app.js注册,回调文件抽离-查库,注册为私密接口),插件3个,但是只要注册接口为私密,不管放在何处,都会接受验证,只关心自己,返回用户信息便于操作 **关键点在于**: 请求头中由Authorization携带token;而jwt生成的token前必须加上'Bearer '。 二、jwt签名&验证一体化 ```js let token = jwt.sign({ phone, userid: response[0].userid, time: Date.now() }, "secret密钥", { expiresIn: 3600 * 12 }); jwt.verify(req.headers.accesstoken, "secret密钥", (err, code) => {// code即为签名时的第一参数}) ``` 特点: 通过router.use注册中间件;需手动过滤白名单接口;顺序很重要,位于他之后的接口都需要验证;只能通过全局变量接收验证结果code在其他接口中使用 cookie ```js router.get('/', async (ctx) => { ctx.cookies.set('userinfo','zhangsan',{ maxAge:60*1000*60, //多长时间以后过期 path:'/news',//特定路径才能访问 domain:'.baidu.com', // 表示 a. baidu.com b.baidu.com 可以共享cookie的数据, //正常情况下不要设置,默认就是当前域下面的所有页面都可以访问 httpOnly:true , //true表示这个cookie只有服务器端可以访问(在页面中通过js访问不到), //false表示服务端和都可以访问 overwrite: true,//默认false,覆盖同名 }) await ctx.render('index.ejs') }) router.get('/news', async (ctx) => { var cookdata = ctx.cookies.get('userinfo') ctx.body = cookdata; }) // bug: 如果设置cookie的值为中文时会报错 // 运用buffer将汉字转换成base64 字符 ,最后再将base64还原会汉字 router.get('/', async (ctx) => { var userinfo = new Buffer('张三').toString('base64') ctx.cookies.set('userinfo',userinfo ,{ maxAge:60*1000*60 }) await ctx.render('index.ejs') }) router.get('/news', async (ctx) => { var cookdata = ctx.cookies.get('userinfo') var userinfo = new Buffer(cookdata ,'base64').toString() ctx.body = userinfo; }) ``` 视图路由中间件: koa-view colors插件改变shell颜色 validator服务端验证表单 async/await || 生成器函数 自定义中间件都没用到,被插件替代