NodeJS使用koa搭建道路检测平台服务器
1:koa使用概述
1.1:koa简介
koa是由Express原班人马打造的,致力于成为一个更小更富有表现力、更健壮的Web框架。使用koa编写web应用,通过组合不同的generator,可以免除重复繁琐的回调函数嵌套,并极大地提升错误处理的效率。koa不在内核方法中绑定任何中间件,它仅仅提供了一个轻量优雅的函数库,使得编写Web应用变得得心应手。
跟express比起来就是难点但是更轻量,市场占用率也是六四开,显然express的市场占用率还是略高一点。对express或者nodeJS基础不太明白的可以看NodeJs教程_Addam Holmes的博客-CSDN博客_nodejs教程
1.2:koa初始化项目
新建一个目录,打开对应目录下的终端然后初始化项目
// 初始化项目
npm init
// package name: 你的项目名字叫啥
// version: 版本号
// description: 对项目的描述
// entry point: 项目的入口文件(一般你要用那个js文件作为node服务,就填写那个文件)
// test command: 项目启动的时候要用什么命令来执行脚本文件(默认为node app.js)
// git repository: 如果你要将项目上传到git中的话,那么就需要填写git的仓库地址(这里就不写地址了)
// keywirds: 项目关键字(我就不写了)
// author: 作者的名字(也就是你叫啥名字)
// license: 发行项目需要的证书(就不写了)
之后安装koa的包
npm i koa
然后新建一个koa的js文件,在根目录新建app.js
之后引入koa,初始化koa,之后监听3000,注册应用级中间件
const Koa = require("koa")
const app = new Koa()
app.use((ctx,next)=>{
// 输出请求路径
console.log(ctx.request.path)
ctx.response.body = "Hello World"
})
app.listen(3000)
第一个参数就是把express的req和res合并了。
ctx.req
Node 的request对象。
ctx.res
Node 的response对象。
- res.statusCode()
- res.writeHead()
- res.write()
- res.end()
绕过Koa的response 处理是不被支持的,应避免使用以下node 属性,下面才是ctx的参数核心
ctx.request()
koa的 Request对象。
ctx.response()
koa的 Respose对象。
1.3:koa精简写法
前面的输出请求路径console.log(ctx.request.path)
可以直接写成ctx.path
输出页面数据ctx.response.body = "Hello World"
也可以直接写成ctx.body = "Hello World"
ctx封装了ctx.request的函数如下(以下访问器和Requst别名等效)
- ctx.header
- ctx.headers
- ctx.method
- ctx.method=
- ctx.url
- ctx.url=
- ctx.originalUrl
- ctx. origin
- ctx.hrefctx.path
- ctx. path=
- ctx.query
- ctx.query=
- ctx. querystring
- ctx. querystring=
- ctx.host
- ctx.hostname
ctx封装了ctx.response的函数如下(以下访问器和Response别名等效)
- ctx.body
- ctx.body=
- ctx. status
- ctx.status=
- ctx. message
- ctx.message=
- ctx.length=
- ctx.length
- ctx.type=
- ctx.type
ctx.headerSent - ctx.redirect()
- ctx.attachment()
- ctx.set()
- ctx. append ()
- ctx.remove ()
- ctx.lastModified=
- ctx.etag=
1.4:koa和express
koa和express区别
(1)更轻量
koa不提供内置的中间件;
koa不提供路由,而是把路由这个库分离出来了(koa/router)
(2)Context对象
koa增加了一个Context的对象,作为这次请求的上下文对象(在koa2中作为中间件的第一个参数传入)。同时Context上也挂载了Request和Response两个对象。和Express类似,这两个对象都提供了大量的便捷方法辅助开发,这样的话对于在保存一些公有的参数的话变得更加合情合理。
(3)异步流程控制
express采用callback来处理异步,koa v2采用async/await。
asyncl await使用同步的写法来处理异步,明显好于callback和promise。
(4)中间件模型
express基于connect中间件,线性模型;
koa中间件采用洋葱模型(对于每个中间件,在完成了一些事情后,可以非常优雅的将控制权传递给下一个中间件,并能够等待它完成,当后续的中间件完成处理后,控制权又回到了自己《非常类似于函数调用》)
koa洋葱模型同步调用案例
const Koa = require("koa")
const app = new Koa()
app.use((ctx,next)=>{
// 判断请求路径
if(ctx.path =="/favicon.ico") return
console.log("11111")
next()
console.log("33333")
ctx.body = "Hello World"
})
app.use((ctx,next)=>{
console.log("22222")
})
app.listen(3000)
koa洋葱模型异步调用案例
接下来异步的调用案例,对于Promise,或者async 和 await忘了就看这个
https://blog.csdn.net/m0_55534317/article/details/127775678
const Koa = require("koa")
const app = new Koa()
app.use(async (ctx, next) => {
if (ctx.path == "/favicon.ico") return
console.log("11111")
let Token = await next()
console.log("44444", ctx.token, Token)
ctx.body = "Hello World"
})
app.use(async (ctx, next)=>{
console.log("22222")
await delay(1000)
ctx.token = "uyguhyugiujhiuyhokyughkj4esaf4s"
console.log("33333")
return "uyguhyugiujhiuyhokyughkj4esaf4s"
})
function delay(time){
return new Promise((resolve,reject)=>{
setTimeout(resolve,time)
})
}
app.listen(3000)
输出就是,页面会等待一秒之后再输出helloworld。
11111
22222
33333
44444 uyguhyugiujhiuyhokyughkj4esaf4s uyguhyugiujhiuyhokyughkj4esaf4s
区别就是koa是再调用next是会把控制权交出去,从哪交出去从哪拿回来
1.5:koa路由
koa的路由需要单独下载
npm i koa-router
之后引入试用一下
const Koa = require("koa")
const Router = require("koa-router")
const app = new Koa()
const router = new Router()
router.get("/list",(ctx,next)=>{
ctx.body = ["Hello World1","Hello World2","Hello World3","Hello World4"]
})
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000)
app.use(router.routes())把所有路由加到app上。
.use(router.allowedMethods())这个是可以当前端路由请求错误可以页面提示
路由有很多种
router.get("/list",(ctx,next)=>{
})
router.post("/list",(ctx,next)=>{
})
router.put("/list:id",(ctx,next)=>{
})
router.del("/list:id",(ctx,next)=>{
})
也可以链式写法
router.get("/list",(ctx,next)=>{
})
.post("/list",(ctx,next)=>{
})
.put("/list/:id",(ctx,next)=>{
})
.del("/list/:id",(ctx,next)=>{
})
1.6:koa路由的规范化
(1)规范api的创建
把所有东西放在一个路由里肯定是不合适的,比如现在有user请求和list请求,那我们就可以建立一个routes文件夹下存放user.js和list.js,然后使用app.js去注册这两个组件即可,app.js需要先引入,先注册成路由级组件,再注册成应用级组件
文件夹结构
user.js
const Router = require("koa-router")
const router = new Router()
router.get("/",(ctx,next)=>{
ctx.body = "user get success"
})
router.post("/",(ctx,next)=>{
ctx.body = "user post success"
})
router.put("/:id",(ctx,next)=>{
ctx.body = {
ok : 1,
data : "user put success"
}
})
router.del("/:id",(ctx,next)=>{
ctx.body = {
ok : 1,
data : "user delete success"
}
})
module.exports = router
list.js
const Router = require("koa-router")
const router = new Router()
router.get("/",(ctx,next)=>{
ctx.body = "list get success"
})
router.post("/",(ctx,next)=>{
ctx.body = "list post success"
})
router.put("/:id",(ctx,next)=>{
ctx.body = {
ok : 1,
data : "list put success"
}
})
router.del("/:id",(ctx,next)=>{
ctx.body = {
ok : 1,
data : "list delete success"
}
})
module.exports = router
app.js
const Koa = require("koa")
const Router = require("koa-router")
const app = new Koa()
const router = new Router()
// 引入两个路由子类
const userRouter = require('./routes/user.js')
const listRouter = require('./routes/list.js')
// 先注册成路由级组件
router.use("/user",userRouter.routes(), userRouter.allowedMethods())
router.use("/list",listRouter.routes(), listRouter.allowedMethods())
// 再注册成应用级组件
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000)
(2)批量注册成路由组建
可以单独写一个文件index.js吧user.js和list.js注册成路由组件,然后app.js仅需引入那一个然后祖册成应用级组件即可。
文件结构(在index.js中组成路由组件),user.js和list.js不变
index.js
const Router = require("koa-router")
const router = new Router()
// 引入两个路由子类
const userRouter = require('./user.js')
const listRouter = require('./list.js')
// 先注册成路由级组件
router.use("/user",userRouter.routes(), userRouter.allowedMethods())
router.use("/list",listRouter.routes(), listRouter.allowedMethods())
module.exports = router
app.js
const Koa = require("koa")
const app = new Koa()
const router = require('./routes/index.js')
// 直接注册成应用级组件
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000)
(3)统一加前缀
首先需要记住一点,user.js、list.js、index.js、app.js的router其实不是同一个router
统一加前缀的命令
router.prefix("/api")
比如如下加在index.js
那么现在user和list的api在访问的时候均需要加/api前缀
但是如果加在user.js中,那就user的api在访问的时候均需要加/api前缀
(4)重定向
router.redirect('/',"/user")
这样127.0.0.1:3000和127.0.0.1:3000/user都可以访问user的get和post
(5)请求静态资源
这个其实我觉得一般用不到现在都前后端分离了
就需要先npm install koa-static
之后再app.js中声明一个文件为静态资源文件夹
const static = require('koa-static')
app.use(static(
path.join(__diname,"public")
))
(6)获取请求参数
GET请求获取请求参数
console.log(ctx.querystring)//获取GET请求的参数字符串
console.log(ctx.query)//获取解析之后的GET请求的参数
POST请求获取请求参数
对于POST请求的处理,koa-bodyparser中间件可以把koa2上下文的formData数据解析到ctx.request.body中
先安装
npm i koa-bodyparser
再引入(index.js中引入)
const bodyParser = require('koa-bodyparser')
// 使用ctx.body解析中间件
app.use(bodyParser())
之后user.js和list.js中就可以输出前端传过来的参数了
console.log(ctx.request.body)
无论前端传form表单还是json格式数据都可以用这个
(7)koa-ejs模板
先安装依赖包
npm install koa-views
npm install ejs
额,又是写前端的,现在都是前后端分离了,就不想写了
2:Socket编程
2.1:WebSocket介绍
WebSocket并不是全新的协议,而是利用了HTTP协议来建立连接。我们来看看WebSocket连接是如何创建的。
首先,WebSocket连接必须由浏览器发起,因为请求协议是一个标准的HTTP请求。,格式如下:
GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13
该请求和普通的HTTP请求有几点不同:
- GET请求的地址不是类似
/path/
,而是以ws://
开头的地址; - 请求头
Upgrade: websocket
和Connection: Upgrade
表示这个连接将要被转换为WebSocket连接; Sec-WebSocket-Key
是用于标识这个连接,并非用于加密数据;Sec-WebSocket-Version
指定了WebSocket的协议版本。
随后,服务器如果接受该请求,就会返回如下响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string
该响应代码101
表示本次连接的HTTP协议即将被更改,更改后的协议就是Upgrade: websocket
指定的WebSocket协议。
版本号和子协议规定了双方能理解的数据格式,以及是否支持压缩等等。如果仅使用WebSocket的API,就不需要关心这些。
现在,一个WebSocket连接就建立成功,浏览器和服务器就可以随时主动发送消息给对方。消息有两种,一种是文本,一种是二进制数据。通常,我们可以发送JSON格式的文本,这样,在浏览器处理起来就十分容易。
为什么WebSocket连接可以实现全双工通信而HTTP连接不行呢?实际上HTTP协议是建立在TCP协议之上的,TCP协议本身就实现了全双工通信,但是HTTP协议的请求-应答机制限制了全双工通信。WebSocket连接建立以后,其实只是简单规定了一下:接下来,咱们通信就不使用HTTP协议了,直接互相发数据吧。
安全的WebSocket连接机制和HTTPS类似。首先,浏览器用wss://xxx
创建WebSocket连接时,会先通过HTTPS创建安全的连接,然后,该HTTPS连接升级为WebSocket连接,底层通信走的仍然是安全的SSL/TLS协议。
服务器支持
由于WebSocket是一个协议,服务器具体怎么实现,取决于所用编程语言和框架本身。Node.js本身支持的协议包括TCP协议和HTTP协议,要支持WebSocket协议,需要对Node.js提供的HTTPServer做额外的开发。已经有若干基于Node.js的稳定可靠的WebSocket实现,我们直接用npm安装使用即可。
2.2:ws模块
(1)ws模块创建客户端并接收到消息群发出去
服务端:
/***
* WebSocket模块
*/
const WebSocket = require("ws")
WebSocketServer = WebSocket.WebSocketServer
const wss = new WebSocketServer({ port: 8800 });
// 一旦有客户端进来这个
wss.on('connection', function connection(ws) {
// 监听客户端发过来的消息
ws.on('message', function message(data, isBinary) {
console.log(data.toString())
wss.clients.forEach(function each(client) {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(data, { binary: isBinary });
}
});
});
ws.send('欢迎加入聊天室');
});
这里面几个注意点
1:创建WebSocket服务端
const wss = new WebSocketServer({ port: 8800 });
2:注意wss是我们创建的WebSocket服务器,ws使我们当前连接的客户端
// 一旦有客户端进来这个
wss.on('connection', function connection(ws) {
// 监听客户端发过来的消息
ws.on('message', function message(data, isBinary) {
console.log(data.toString())
wss.clients.forEach(function each(client) {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(data, { binary: isBinary });
}
});
});
ws.send('欢迎加入聊天室');
});
3:client.readyState === WebSocket.OPEN
判断当前客户端是连接的状态
4:wss.clients
是我们创建的WebSocket服务器一个方法,wss.clients.forEach(function each(client)
就是遍历全部的WebSocket客户端
客户端具体细节见WebSocket学习笔记_Addam Holmes的博客-CSDN博客_await websocket
var ws = new WebSocket("ws://localhost:8080")
ws.onopen = ()=>{
console.log("open")
}
ws.onmessage = (evt)=>{
console.log(evt.data)
}
(2)在第一次连接的时候记录用户信息
/***
* WebSocket模块
*/
const WebSocket = require("ws")
WebSocketServer = WebSocket.WebSocketServer
const wss = new WebSocketServer({ port: 8800 });
// 一旦有客户端进来这个
wss.on('connection', function connection(ws,req) {
// 连接时得操作
const websiteType = new URL(req.url,"http://127.0.0.1:3000")
console.log(websiteType.searchParams.get("name"))
if (websiteType.searchParams.get("name") === "website"){
// 引擎端
ws.name = "website"
ws.send('欢迎使用');
}else {
// 引擎端
ws.name = "car"
}
// 监听客户端发过来的消息
ws.on('message', function message(data, isBinary) {
// console.log(data.toString())
wss.clients.forEach(function each(client) {
if (client.name == "website" && client.readyState === WebSocket.OPEN) {
client.send(data, { binary: isBinary });
}
});
});
});
在使用的时候可以在初次连接的时候根据连接的路由不同打上标签
const websiteType = new URL(req.url,"http://127.0.0.1:3000")
console.log(websiteType.searchParams.get("name"))
if (websiteType.searchParams.get("name") === "website"){
// 引擎端
ws.name = "website"
ws.send('欢迎使用');
}else {
// 引擎端
ws.name = "car"
}
所以客户端连接的时候就需要注意了
ws://127.0.0.1:8800
// websiteType.searchParams.get("name") == null
ws://127.0.0.1:8800?name=website
// websiteType.searchParams.get("name") == website
此时我们可以判断连接路由为ws://127.0.0.1:8800?name=website得ws加个属性,属性名为name,属性值就是website,那其他的客户端的name属性属性值就是car
// 监听客户端发过来的消息
ws.on('message', function message(data, isBinary) {
// console.log(data.toString())
wss.clients.forEach(function each(client) {
if (client.name == "website" && client.readyState === WebSocket.OPEN) {
client.send(data, { binary: isBinary });
}
});
});
在转发数据的时候只转发给name为website的客户端
}
所以客户端连接的时候就需要注意了
```js
ws://127.0.0.1:8800
// websiteType.searchParams.get("name") == null
ws://127.0.0.1:8800?name=website
// websiteType.searchParams.get("name") == website
此时我们可以判断连接路由为ws://127.0.0.1:8800?name=website得ws加个属性,属性名为name,属性值就是website,那其他的客户端的name属性属性值就是car
// 监听客户端发过来的消息
ws.on('message', function message(data, isBinary) {
// console.log(data.toString())
wss.clients.forEach(function each(client) {
if (client.name == "website" && client.readyState === WebSocket.OPEN) {
client.send(data, { binary: isBinary });
}
});
});
在转发数据的时候只转发给name为website的客户端