第十一节: 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)
})


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=18
    
ctx.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>h1</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 data = ''
            ctx.req.on('data', function(chunk){
                data += chunk;
            })
            ctx.req.on('end', function(){
                let postdata = 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 = 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 引入静态资源目录里的资源

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

<link rel="stylesheet" href="/style/index.css" />
<!-- 此时style前的/ 代指我们设置的静态资源目录,即join(__dirname,'static')拼接出来的路径 -->


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({})传入一个对象.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • koa2框架笔记 Node.js是一一个异步的世界,官方API支持的都是callback 形式的异步编程模型,这会...
    wanminglei阅读 536评论 0 0
  • koa2框架笔记 Node.js是一一个异步的世界,官方API支持的都是callback 形式的异步编程模型,这会...
    wanminglei阅读 1,865评论 0 2
  • 框架提出的背景 ES6/7带来的变革 自ES6确定和ES7中async/await开始普及,Node的发展变得更加...
    宫若石阅读 8,459评论 1 14
  • Koa2 是由 Express 团队打造的下一代 Node.js Web 框架,基于 ES7 的 async/aw...
    Paranoid_K阅读 9,347评论 2 23
  • koa2官网: https://koa.bootcss.com/ 一. 安装koa2 Koa 依赖 node v7...
    熊爸天下_56c7阅读 2,031评论 1 2