深入探索Koa框架:构建高效Node.js Web服务
前言
在Node.js中,如果我们要实现一个Web服务,那么我们就就必须要引入http
模块,再创建一个Web服务,然后对端口进行监听.对此我们可以使用Koa
框架方便我们创建一个Web服务,那么什么又是Koa
呢?
Koa是一个基于Node.js平台的Web开发框架,由Express框架的创造者所开发。Koa的设计目标是提供一个更小、更富有表现力和更健壮的Web框架。它使用ES6的async/await功能来简化异步代码的编写,避免了回调地狱(Callback Hell)的问题,从而提高了代码的可读性和维护性。Koa并不在核心中捆绑任何中间件,而是提供一套优雅的函数库,允许开发者自由选择和组合中间件,这使得编写Web应用变得更加灵活和高效。
使用Koa
Koa对比原生Node.js
首先我们先使用原生的Node.js
创建一个Web服务。
// 使用原生的node实现一个Web服务,就必须要引入http模块
const http = require('http');
//创建一个Web服务,这里接受一个回调函数
const server = http.createServer((req,res)=>{
// req 请求分为请求头 请求体 res响应
res.end('hello world')
})
//监听
//这里也可以接受一个回调函数
server.listen(3000,()=>{
console.log('3000 端口启动');
})
然后我们再安装Koa
依赖
npm i koa
安装好依赖,创建文件demo1.js.我们使用Koa
我们就需要引入Koa
的模块const Koa = require('koa')
然后new 一个Koa
,因为其源码被写成了一个函数,所以我呢吧需要new.相较于原生的Node.js
是使用响应体res
向前端输出数据,那么在koa
中又是如何实现的呢?在Koa
中我们需要自定义一个函数,该函数接受一个参数ctx
,这个参数就是Koa
的上下文对象,使用这个上下文对象,我们能实现原生Node.js
的所有用法,我们打印ctx
它同样包含了自己封装的请求体育响应体,甚至原生Node.js
的req
以及res
.所以在Koa
中使用ctx.res.end
也可以实现数据挂载在前端页面,也可以使用Koa
本身的ctx.response.body = 挂载内容'
,除了这两种方法也存在一种代理方法,直接使用ctx.body= 挂载内容'
也可以实现挂载。为了使我们自定义的函数生效,我们必须使用New Koa
返回的属性调用use
方法
const Koa = require('koa');
//必须New Koa其源码被写成了一个函数
const app = new Koa();
//向全端输出内容 自定义一个函数,app.use()
//ctx 是Koa的上下文对象
//koa中的ctx集成了req 以及res
const main = (ctx)=>{
console.log(ctx);
// ctx.res.end = 'Hello world' 响应体挂数据
// ctx.response.body = 'Hello World';
ctx.body = 'Hello World';//代理 访问子对象中的body
}
app.use(main)
app.listen(3000,()=>{
console.log('3000端口启动');
})
请求数据处理
当前端向后端发送数据请求时,后端需要读取到前端请求数据的格式,在Koa
中存在一个方法accepts()
里面写数据格式,就可以读取到了
const Koa = require('koa');
//必须New Koa其源码被写成了一个函数
const app = new Koa();
//向全端输出内容 自定义一个函数,app.use()
//ctx 是Koa的上下文对象
//koa中的ctx集成了req 以及res
const main = (ctx)=>{
if(ctx.request.accepts('xml')){
//如果前端请求的是一个xml数据,则返回
ctx.body = '<data>Hello World</data>'
}else if(ctx.request.accepts('html')){//会读取到前端请求头中的accept属性
//如果前端请求的是一个html数据,则返回
ctx.body = '<p>Hello World</p>'
}
}
app.use(main)
app.listen(3000,()=>{
console.log('3000端口启动');
})
假设我引入一个html
文件呢?
这里我们就需要使用fs文件系统了
,在html
文件中我们事先写好
const context = fs.readFileSync('./template.html','utf-8')
fs文件系统
返回的是一个buffer
流,但是浏览器是无法读取流这一个数据类型的,所以就会触发下载。为了浏览器能读取,我们就需要设置编码格式就好了。我们也可以换一种写法,我们创建一个可读流,设置请求头,或者使用ctx.response.type = 'html'
也可以实现读取html
文件
// ctx.response.type = 'html' 同理也可以用ctx.type
ctx.res.writeHead(200,{'Content-Type':'text/html'})//也可以用原身的node写
// 创建一个可读流 依旧是一个十六进制的buffer流
const context = fs.createReadStream('./template.html')
// console.log(context);
ctx.body = context
路径处理
const main = (ctx)=>{
//判断前端请求的路径
if(ctx.url === '/'){
ctx.type = 'html';
ctx.body = '<h2>首页</h2>'
}else {
ctx.type = 'html';
ctx.body = '<a href="/">去首页</a>'
}
}
页面路径如果这样写的话,假设我们很多条数据,我们就必须写很多个if-else
代码的可读性就会变差,所以在koa
中也有路由的说法koa-route
,我们先安装koa-route
npm i koa-route
引入koa-route
const router = require('koa-route');
实现路径的切换,在koa-route
中有一个get('url',function)
接受路径,以及执行函数。
const main = (ctx)=>{
ctx.type = 'html'
ctx.body = '<h2>首页</h2>'
}
const about = (ctx)=>{
ctx.type = 'html'
ctx.body = '<a href="/">关于首页,点击去首页</a>'
}
app.use(router.get('/',main))//第二参数写mian 默认main会生效
app.use(router.get('/about',about))
但是如果请求失败,我们又应该如何知道呢,这里我们就可以自己写一个日志生成,方便检查错误,但是
const logger = (ctx)=>{//后端日志输出 前端请求路径
console.log(`${ctx.url}-${ctx.method}-${Date.now()}`);//日志,方便检查出错
}
app.use(logger)
如果这样写的话就会出问题,在koa
中use
一旦前面的use
执行完成之后,就不会执行后续的use
了,所以在自定义函数中,我们需要传入一个next
方法,并调用
const Koa = require('koa');
const router = require('koa-route');
//必须New Koa其源码被写成了一个函数
const app = new Koa();
//向全端输出内容 自定义一个函数,app.use()
//ctx 是Koa的上下文对象
//koa中的ctx集成了req 以及res\
const logger = (ctx,next)=>{//后端日志输出 前端请求路径
console.log(`${ctx.url}-${ctx.method}-${Date.now()}`);//日志,方便检查出错
next()//在你完成你要的逻辑之后,需要调用next() 让下一个use生效
}
const main = (ctx)=>{
ctx.type = 'html'
ctx.body = '<h2>首页</h2>'
}
const about = (ctx)=>{
ctx.type = 'html'
ctx.body = '<a href="/">关于首页,点击去首页</a>'
}
// app.use(main)
//当浏览器
app.use(logger)//必须在路由之前
app.use(router.get('/',main))//第二参数写mian 默认main会生效
app.use(router.get('/about',about))
app.listen(3000,()=>{
console.log('3000端口启动');
})
洋葱模型
我们来看看app.use
的执行过程
const Koa = require('koa');
const app = new Koa()
const one = (ctx,next)=>{ //中间件
console.log(1);
next()
console.log(2);
}
const two = (ctx,next)=>{
console.log(3);
next()
console.log(4);
}
const three = (ctx)=>{
console.log(5);
console.log(6);
}
app.use(one)
app.use(two)
app.use(three)
app.listen(3000,()=>{
console.log('3000端口启动');
})
我们执行代码,终端会先输出1 3 5 6
之后会输出什么呢?
我们来看看结果
1 3 5 6 4 2
.
是不是有点像递归
先执行one
中的第一个输出,遇见next
后就执行下一个app.use
当最后一个app.use
执行完毕,就执行上一个app.use
.所以app.use
的实现原理就是递归,这一过程被称为洋葱模型
感谢大家阅读本文,若有不足,恳请指出,谢谢大家!!!
转载自:https://juejin.cn/post/7390815289993560118