第十一节:|第十一节: Node框架: Koa2

1. 认识Koa Koa 是基于Node平台的下一代web开发框架


1.1 简介 koa 是由 Express 原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的 Web 框架。使用 koa 编写 web 应用,通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套,并极大地提升错误处理的效率。koa 不在内核方法中绑定任何中间件,它仅仅提供了一个轻量优雅的函数库,使得编写 Web 应用变得得心应手。开发思路和express 差不多, 最大的特点是可以避免异步嵌套


1.2 关于异步 Node.js是一个异步编程的世界, 官方支持的API都是callback形式的异步编程模型,
回调函数处理异步的问题

  1. callback嵌套,形成的回调地狱问题
  2. 异步函数中的可能同步调用callback返回值数据


1.3 了解Koa2 Koa v2 的核心是使用面向更新特性的 async 函数的中间件,其正式版本发布前后略有不同。
  • 正式版本发布前:支持 3 种中间件的写法(Promise,Generator,async函数)。
  • 正式版本发布后:推荐使用 ES2017 的 async 函数作为中间件。


1.4 为什么选择 Koa 为什么要在众多的框架中选择 Koa 呢,基本都是网上找到的选择Koa的理由,供其参考。
  1. 用 async 函数做异步流程控制时,代码更容易理解。
  2. 错误处理的干干净净。无论是 async 函数还是 Promise 规范都能很好地处理异常,另外 Koa 继承自 Event 的,结合 ctx 里的一些 API 能够更简单地处理错误。
  3. 具有优雅的回形针中间件机制。通过更少的代码可以完成更多的工作,尤其是能对响应部分做拦截。
  4. 性能非常好。(跟 Express 作对比)
  5. Koa 核心代码量比较少,易于定制,易于在其上开发各种 Web框架。
  6. 社区生态逐渐完善。
  7. 国内外很多公司都已经大量应用 Koa 了,目前 Node.js 的首选 Web 架构是 Koa。
  8. 拥有 Egg.js (基于 Koa 的成熟的企业级 Web 开发框架),拥有庞大的插件生态。
  9. 拥有 MidwayJS。它基于 Egg.js 生态,使用 TS 编写,提供 IoC 容器,是面向未来的框架。


2. 使用示例 2.1. 初始化化项目 安装koa包
$ npm install --save koa # or $ cnpm install --save koa #or $ yarn add koa



2.2 编写程序代码
// 1. 引入koa 生成实例 const Koa = require('koa'); // `Koa` 是一个构造函数需要使用new 调用 const app = new Koa; // 2. 绑定中间件(异步函数use的参数) // use 就是把中间件绑定到实例上的方法 // 自定义中间件,中间件会有两个参数ctx,next //Koa把req和res对象汇聚到了ctx上 // use可以注册n个中间件 app.use(async (ctx,next) => { ctx.body = "hello koa2" })// 3. 监听端口 app.listen(3000)



2.3 启动应用 通过node 命令启动应用
node app.js



3. 应用程序 3.1 app.use() 挂在中间件
为应用添加中间件,可以注册多个中间件
app.use(async (ctx,next) => {}) app.use(async (ctx,next) => {}) app.use(async (ctx,next) => {})



3.2 app.context 扩展上下文对象
app.context是从中创建ctx的原型。 可以通过编辑app.contextctx添加其他属性。
app.context.name = 'haha' app.use((ctx, next) => { console.log(33)ctx.body = 'hello world' console.log(55) })

【第十一节:|第十一节: Node框架: Koa2】

3.4 app.callback() 返回适用于 http.createServer() 方法的回调函数来处理请求。你也可以使用此回调函数将 koa 应用程序挂载到 Connect/Express 应用程序中。
http.createServer(app.callback()).listen(3000) http.createServer(app2.callback()).listen(3001)



3.3 错误处理 如果发生错误时,会触发error事件
app.on('error', err => { log.error("server error", err) })



4. Context 上下文 4.1. 了解Context Koa Context 将 node 的 requestresponse 对象封装在一个单独的对象里面,其为编写 web 应用和 API 提供了很多有用的方法。
context 在每个 request 请求中被创建,在中间件中作为接收器(receiver)来引用,或者通过 this 标识符来引用:
app.use(async ctx => { ctx; // is the Context ctx.request; // is a koa Request ctx.response; // is a koa Response });

许多 context 的访问器和方法为了便于访问和调用,简单的委托给他们的 ctx.requestctx.response 所对应的等价方法, 比如说 ctx.typectx.length 代理了 response 对象中对应的方法,ctx.pathctx.method 代理了 request 对象中对应的方法。


4.2. Context API
API 描述
ctx.req request 对象.
ctx.res response 对象.
ctx.request Request 对象.
ctx.response Response 对象.
ctx.cookies.get(name, [options]) 通过 options 获取 cookie name:
ctx.cookies.set(name, value, [options]) 通过 options 设置 cookie namevalue :


4.3.Response 别名 以下访问器和 Response 别名等效:
  • ctx.status
  • ctx.message
  • ctx.length
  • ctx.type
  • ctx.redirect()


4.4. ctx的属性与方法 4.4.1 向前端返回数据 ctx.body
ctx.body = '返回数据'; //也可以对象ctx.body = { a:1 } //Koa会将对象封装成字符串传递个前端



4.4.2 获取前端路径 ctx.url
等同于ctx.request.url
//比如 网址输入localhost:3000/path?name=wuwei&age=18 //返回 /path?name=wuwei&age=18 app.use(async function(ctx,next){ // 请求路径 console.log(ctx.url); ctx.body='hello wrold' })



4.4.3 获取请求的方法 ctx.method
app.use(async function(ctx,next){ // 请求方式 console.log(ctx.method); ctx.body='htllo world'})



4.4.4 中间件之间的传值 用于通过中间件传递信息和你的前端视图。
ctx.state


4.4.5 获取非查询的路由 ctx.path
//比如 网址输入localhost:3000/path?name=wuwei&age=18ctx.path; //返回 /path



4.4.6 将路由中的查询部分转成对象 ctx.query
//比如 网址输入localhost:3000/path?name=wuwei&age=18 //返回 { name:wuwei,age:18} app.use(function(ctx,next){ // 获取querystring解析后的对象 console.log(ctx.query); ctx.body='hello world'})



4.4.7 只获取路由中查询部分 ctx.querystring
//比如 网址输入localhost:3000/path?name=wuwei&age=18 //返回 name=wuwei&age=18 app.use(function(ctx,next){ //后去queryString console.log(ctx.querystring); ctx.body='hello world'



4.4.8. 设置返回给前端的响应头
// 响应状态 ctx.status = 201// 响应类型 ctx.type = 'html'// 返回数据 ctx.body = 'h1'



5. Koa路由 路由(Routing)是一个URL(或者叫路径)和一个特定的HTTP方法(GET,POST等)组成, 涉及到应用如何响应客户端对某个网站节点的访问
简单理解就是路由即根据不同 的URL 地址, 加载不同的页面实现不同的功能
Koa路由和Express 路由有所不同,在Express中直接引入Express就可以配置路由了, 但是在Koa中我们需要安装对应的koa-router路由模块来实现


5.1. 先安装koa-router
$ npm install --save koa-router



5.2. koa路由的使用 需要引入koa-router模块, 然后实例化
const Koa = require('koa'); // 1. 引入路由模块 const Router = require('koa-router'); const app = new Koa; // 2.路由实例 const router = new Router; // 3.定义路由 router.get("/", async ctx => { ctx.body = "首页" })// 4.主程序使用路由 app.use(router.routes()); // 启动路由 app.use(router.allowedMethods()); /* allowedMethods方法的作用 官方推荐用法, 我们可以理解allowedMethods方法用在routes方法之后, 当所有路由中间件最后调用 */// 监听端口 app.listen(3000)



5.3. 路由设计 5.3.1 get 请求方式 设计get请求方式的路由
指定路由请求方式为get方式, 请求地址为/ ,当这些匹配 成功后,会触发后面的的中间件
router.get('/' , async (ctx,next)=>{})



5.3.2 post 请求方式 设计post请求方式的路由
指定路由请求方式为post方式, 请求地址为/ ,当这些匹配 成功后,会触发后面的的中间件
router.post('/' , async ctx,next)=>{})

不同请求方式中的中间件是不会串联的.不会通过next()方法转移控制权的


5.3.3 为一个路由配置多个中间件 如果想在同一个路由中使用多个中间件,只需要传递多个参数即可,如
router.get('/' , async (ctx,next)=>{ await next(); }, async (ctx,next)=>{}, async (ctx,next)=>{}) //这里的中间件是串联的可以通过`next()`移交控制权



5.4. 父子路由分发 使用router分发路由
// 主路由 const router =new Router; // student 子路由 const student =new Router; student.get("/", ctx =>{ ctx.body ="学生页面" })// teacher子路由 const teacher =new Router; teacher.get("/", ctx =>{ ctx.body ="老师页面" })// 将子路由挂在到主路由上 router.use("/student",student.routes(),student.allowedMethods()) router.use("/teacher",teacher.routes(),teacher.allowedMethods())app.use(router.routes()) app.use(router.allowedMethods())



5.5. 路由传值 5.5.1 get 路由传值 Koa2 中 GET 传值通过request 接受, 接收的方法有两种:query 和 querystring
  1. query: 返回的是格式化好的参数对象
  2. querystring: 返回的是请求字符串
router.get("/", async (ctx,next) => { consol.log(ctx.query) // 推荐使用 consol.log(ctx.querystring) consol.log(ctx.request.query) consol.log(ctx.request.querystring) })



5.5.2 动态路由传值
router.get('/new/:aid', async (ctx,next) => { console.log(ctx.params) // 获取动态路由的值 })



5.5.3 POST 路由传值 有两种方案
  1. 原生node获取post数据
  2. Koa中的koa-bodyparser中间件的使用
原生获取post数据
// 原生获取post数据的方法 function parsePostData(ctx){ return new Promise((resolve,reject) => { try{ let datahttps://www.it610.com/article/= '' ctx.req.on('data', function(chunk){ data += chunk; }) ctx.req.on('end', function(){ let postdata = https://www.it610.com/article/parseQueryString(data) resolve(postdata) }) }catch(error){ reject(error) } }) }// 将字符串参数处理成对象 function parseQueryString(querystring){ const queryData = {}; const qsList = querystring.split("&"); for(let [index, qs] of qsList.entries()){ let kvList = qs.split("="); queryData[kvList[0]] = decodeURIComponent(kvList[1]); } return queryData; };

通过koa-bodyparser 获取post数据
使用流程
  1. 下载koa-bodyparser包
    $ npm install koa-bodyparser --save

  2. 引入包
    const bodyParser = require('koa-bodyparser')

  3. 使用中间件
    app.use(bodyParser())

  4. 在ctx中request的body属性中获取数据
    ctx.request.body

使用示例
router.get('/', async ctx => { let postData = https://www.it610.com/article/ctx.request.body; })



6. 静态资源中间件koa-static 静态资源中间件 静态web服务


6.1 安装
$ npm install koa-static --save

6.2 引入模块
const static = require('koa-static');



6.3 使用static中间件 其实router还是static对于app来说都是中间件
app.use(static())

使用static中间件最好在使用router中间件之前


6.4 设置静态资源目录 在使用static中间件时,设置静态资源目录
需要使用path模块下的join()方法拼接静态资源目录的路由
app.use(static(join(__dirname,'static')))



6.5 可以配置多个静态资源目录
app.use(static(join(__dirname,'static'))) app.use(static(join(__dirname,'public')))



6.6 引入静态资源目录里的资源 引入静态资源目录里的资源需要使用绝对路径



7 koa2使用模板引擎 7.1 安装 安装koa-views
npm install --save koa-views

安装ejs
npm install --save ejs



7.2 使用模板引擎
// 引入模块 var views = require('koa-views'); // 配置视图模板 app.use(views(__dirname + '/views', { extension: "ejs" })); // 渲染模板引擎 ctx.render("index",{ title:"hello world" })



8. 解决跨域问题 8.1 通过设置响应头解决跨域
app.use(async (ctx, next) => { // 允许所有域名请求 ctx.set("Access-Control-Allow-Origin", "*") //只允许 http://localhost:8080 域名的请求 // ctx.set("Access-Control-Allow-Origin", "http://localhost:8080"); // 设置允许的跨域请求方式 ctx.set("Access-Control-Allow-Methods", "OPTIONS, GET, PUT, POST, DELETE")// 字段是必需的。值一个逗号分隔的字符串,表示服务器所支持的所有头信息字段. ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type")// 服务器收到请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,即可做出响应。// Content-Type表示具体请求中的媒体类型信息 ctx.set("Content-Type", "application/json; charset=utf-8")// 该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。 当设置成允许请求携带凭证cookie时,需要保证"Access-Control-Allow-Origin"是服务器有的域名,而不能是"*"; ctx.set("Access-Control-Allow-Credentials", true); // 该字段可选,用来指定本次预检请求的有效期,单位为秒。 // 当请求方法是PUT或DELETE等特殊方法或者Content-Type字段的类型是application/json时,服务器会提前发送一次请求进行验证 // 下面的的设置只本次验证的有效时间,即在该时间段内服务端可以不用进行验证 ctx.set("Access-Control-Max-Age", 300); /* CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段: Cache-Control、 Content-Language、 Content-Type、 Expires、 Last-Modified、 Pragma。 */ // 需要获取其他字段时,使用Access-Control-Expose-Headers, // getResponseHeader('myData')可以返回我们所需的值 ctx.set("Access-Control-Expose-Headers", "myData")await next() })



8.2 使用包解决跨域 原生的可以设置res解决跨域问题
这里可以通过模块解决跨域问题
  1. 安装解决跨域的模块
$ npm install @koa/cors@2 --save

  1. 导入模块
const cors = require('@koa/cors');

  1. 使用cors中间件
app.use(cors())

这样就可以解决跨域啦


9.文件上传 使用koa-body处理文件上传
9.1 koa-body处理post数据
const Koa = require("koa"); const Router = require("koa-router"); const koaBody = require('koa-body'); const app = new Koa; const router = new Router; // 挂在中间件 app.use(koaBody()); router.post("/upload",ctx => { ctx.body = `Request Body: ${JSON.stringify(ctx.request.body)}`; })app.use(router.routes())// 监听端口 app.listen(3000)



9.2 body-body处理文件上传 koa-body不仅能处理图片上传,还能处理数据
const Koa = require('koa'); const koaBody = require('koa-body'); const { join } = require('path'); const app = new Koa; app.use(koaBody({ multipart: true, formidable:{ // 上传存放的路劲 uploadDir: join(__dirname,'upload'), // 保持后缀名\ keepExtensions: true, // 文件大小 maxFileSize: 1024, onFileBegin: (name, file) => { // 获取后缀, 如: .js.txt const reg = /\.[A-Za-z]+$/g const ext = file.name.math(reg)[0]//修改上传文件名 file.path = join(__dirname,"upload/") + Date.now() + ext; }, onError(err){ console.log(err) } } }))app.use(async (ctx)=>{ // 表单数据在body console.log(ctx.request.body); // 文件在files console.log(ctx.request.files); ctx.body = '上传成功' })app.listen(3002)

如果需要处理数据,就不需要给中间件koaBody()传任何参数,如果需要传文件,则需要给中间件koaBody({})传入一个对象.

    推荐阅读