NodeJS—学习笔记
学习coderwhy的《深入Node.js》课程的一些小总结。
三、http模块
1.开启服务器的方式
- http.createServer((req,res)=>{})
- new http.Server((req,res)=>{})
Server通过listen方法来开启服务器
listen的参数
- 端口port:可以不传,会默认分配
- 主机host:默认0.0.0.0
- localhost:是一个域名,会被解析成127.0.0.1
- 127.0.0.1:回环地址,主机自己发出的包会被自己接受
- 正常数据包经过 应用层-传输层-网络层-数据链路层-物理层
- 回环地址在网络层直接就被捕获到了
- 监听127.0.0.1时,不能够通过本地ip访问到服务器
- 0.0.0.0:监听ipv4上所有地址,再根据端口找到不同应用,可以通过本地ip访问到服务器
2.request对象
- req.url:能够获取请求的url 如 /login?username=why&password=123
- 通过url内置模块(node内置)解析req.url
- 解析成一个对象,里面包含req.url的信息
- const { pathname,query } = url.parse(req.url)
- 通过querystring内置模块(node内置)解析处理得到的query(username=why&password=123)
- qs.parse(query) —> {username:why,password:123}
- 通过url内置模块(node内置)解析req.url
- req.method:获取请求方式 如 GET、POST
- GET:查询数据
- POST:新建数据
- PATCH:更新数据
- DELETE:删除数据
- req.headers
- keep-alive:保持连接
- http基于tcp,通常在一次请求和响应后会立刻中断
- http1.0中想要保持连接需要在浏览器请求头和服务器响应头中添加connection:keep-alive
- http1.1,默认connection:keep-alive,不同web服务器有不同保持时间,node中默认5s
- accept-encoding:告知服务器,客户端支持的文件压缩格式,比如js文件可以使用gzip编码,对应.gz文件
- 通过压缩可以提高文件传输的效率,可以通过webpack配置将大文件进行压缩优化项目
- accept:浏览器可接收的文件类型
- user-agent:包含客户端信息
- keep-alive:保持连接
如何获取到post请求body中的数据?
- body中的数据是通过流的方式写入的,通过req.on(“data”,(data)=>{})监听data事件,获取data中的数据流(buffer)
- 解码方式
- data.toString() 默认utf-8
- req.setEncoding(‘utf-8’)
- 文本 utf-8
- 照片 音频 视频 binary
- 解码之后拿到JSON对象,通过JSON.parse(data)转为对象
3.response对象
- 响应结果 res.end(“xxx”) 本质上 res.write(“xxx”) res.end()
- 设置响应的header
- res.setHeader(“Content-Type”, “text/plain;charset=utf8”)
- res.writeHead(200, {
“Content-Type”: “text/plain;charset=utf8”
})
4.文件上传
方法一:用传统的方式,手动获取文件数据
- 不能直接拿到从客户端发送过来的文件数据,然后进行写入,因为这些数据中不仅仅包含文件本身的数据,还包含与文件相关的信息数据,如果直接用未处理的文件数据那么就无法还原成原本的文件
- 为了获取文件数据,如png,必须获取除文件信息数据和边界符之外的文件数据
// 上传文件必须把编码改成 binary
req.setEncoding("binary");
let body = "";
// 拿到文件边界符
let boundary = req.headers["content-type"].split(";")[1];
boundary = boundary.split("=")[1];
req.on("data", (data) => {
body += data;
});
req.on("end", () => {
// 处理body
// 1.获取image/png的位置
const payload = qs.parse(body, "\r\n", ": ");
const type = payload["Content-Type"];
// 2.开始在image/png的位置进行截取
const typeIndex = body.indexOf(type);
const typeLength = type.length;
let imgData = body.substring(typeIndex + typeLength);
// 3.将中间两个空格去掉
imgData = imgData.replace(/^\s\s*/, "");
// 4.将最后的boundary去掉
imgData = imgData.substring(0, body.indexOf(`--${boundary}--`));
fs.writeFile("./foo.png", imgData, { encoding: "binary" }, (err) => {
console.log("文件上传成功");
res.end("文件上传成功");
});
四、Web框架
1.Express框架
本质上是一系列中间件函数的调用
(1)express中间件
本质是传递给express的一个回调函数
- 三个参数:
- request
- response
- next函数(执行下一个中间件的函数)
- 中间件可以用来干什么?
- 执行任何代码
- 更改request和response
- 结束请求-响应周期(返回数据)
- 调用下一个中间件
- 如果当前中间件没有结束请求-响应周期,则必须调用next()将控制权传递给下一个中间件,否则请求将被挂起
- 中间件可以连续注册,利用next()调用下一个中间件
app.get('/login',(req,res,next)=>{next()},(req,res,next)=>{next()},(req,res,next)=>{})
(2)使用中间件来解析request body
- 原生方法
- 使用express内置中间件解析json、urlencoded、form-data数据
- express.json() => 使用app.use(express.json())
- express.urlencoded({extended:true})
- true:对urlencoded进行解析时,用的是第三方库:qs
- false:对urlencoded进行解析时,用的是Node内置模块:querystring
- 通过第三方库 multer对form-data进行解析 ```javascript const multer = require(‘multer’) const upload = multer()
app.post(‘/login’,upload.any(),(req,res,next)=>{ console.log(req.body) // 文本信息会放在body中 }) // 解析所有文件,详细看文档
<a name="ZTwNr"></a>
#### (3)使用multer来上传文件
- 不自定义文件信息
```javascript
const multer = require('multer')
// 当启动服务器时,会检查是否有该目录,没有的话就会默认创建
const upload = multer({
dest:'./uploads/' // 文件保存的位置
})
// 使用中间件来解析上传的文件
app.post('/upload',upload.single(filename),(req,res,next)=>{
console.log(req.file) // 文件信息会放在req.file、req.files中
console.log(req.files)
// 单个文件时,使用 upload.single(filename)
// 多个文件时,使用 upload.array(fieldname[, maxCount])
})
- 通过storage自定义文件信息
// 2.通过storage自定义文件信息
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, '/tmp/my-uploads')
},
filename: function (req, file, cb) {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9)
cb(null, file.fieldname + '-' + uniqueSuffix)
}
})
const upload = multer({ storage: storage })
(4)使用morgan将请求的日志保存,用法看github文档
(5)获取get请求中url数据
- URL的params
URL:localhost:8000/login/03211/coder
app.get("/login/:id/:name", (req, res, next) => {
console.log(req.params);
res.end("请求成功");
});
==> { id: '03211', name: 'coder' }
- URL的query
URL:localhost:8000/login?username=coder&password=123
app.get("/login", (req, res, next) => {
console.log(req.query);
res.end("请求成功");
});
==> { username: 'coder', password: '123' }
(6)处理错误逻辑
- next(err)中间件中可以传入参数,该参数包含错误信息(注意如果传入参数,那么会执行处理错误的中间件,而不会执行下一个中间件)
- next(new Error(“error message”))
- 通过app.use((err,req,res,next)=>{ 错误逻辑处理 })中间件做统一的错误处理
- 当回调函数是四个参数时,会告知是错误处理的回调函数
(7)静态服务器
- 通过app.use(express.static(path))来指定静态资源位置
(8)Express路由
- 目的:防止app中代码逻辑过于复杂,使用路由进行抽取,更利于维护
- 使用express.Router来创建一个路由处理程序
- 一个Router实例拥有完整的中间件和路由系统
- 它被称为迷你应用程序(mini-app)
- 用法:
const express = require('express')
const userRouter = express.Router()
userRouter.get('/',(req,res,next)=>{})
userRouter.post('/',(req,res,next)=>{})
userRouter.delete('/',(req,res,next)=>{})
// 使用注册后的路由
app.use('/users',userRouter)
2.Koa框架
koa是一个更加轻量级的框架,相对比express具有更强的异步处理能力
(1)Koa中间件
- koa注册中间件提供两个参数
- ctx:上下文(context)对象
- ctx.request:获取请求对象
- ctx.response:获取响应对象
- next:本质上是dispatch,它返回一个promise
- 当所有中间件都执行完毕后才执行then进行响应,这也就是为什么设置ctx.response.body后还能继续设置,且最终是以最后的body进行响应
- 而express是执行完res.end()后就已经做出响应了
- ctx:上下文(context)对象
- koa通过创建app对象,注册中间件只能通过use方法
- 没有提供以下注册方式
- methods方式:app.get()/.post
- path方式:app.use(‘/home’,()=>{})
- 连续注册方式:app.use((ctx,next)=>{},(ctx,next)=>{})
(2)Koa路由
- Koa中并没有内置的路由,需要通过第三方库:koa-router来实现
- 因为koa中间件没有methods的方式,但koa-router中实现了methods的方式
- koa路由中也实现了path、连续注册的注册方式
- 具体使用方式 ```javascript // user.js const Router = require(“koa-router”); const userRouter = new Router({ prefix: “/users” });
userRouter.get(“/“, (ctx, next) => { ctx.response.body = “user list”; });
userRouter.post(“/“, (ctx, next) => { ctx.response.body = “create user info”; });
module.exports = userRouter;
```javascript
const Koa = require("koa"); // 导出是个类
const app = new Koa();
const userRouter = require("./router/user");
// 注册路由
app.use(userRouter.routes());
// 判断methods是否支持
app.use(userRouter.allowedMethods());
app.listen(8000, () => {
console.log("8000服务器已开启");
});
- allowedMethods用于判断某一个method是否支持
- 如果不支持get、put、delete、patch会自动报错:Method Not Allowed,状态码:405
- 如果不支持link、copy、lock会自动报错:Not Implemented,状态码:501
(3)参数解析
- 通过路由解析params、query
- ctx.request.params
- ctx.request.query
- 使用koa-bodyparser中间件(第三方库)解析JSON、urlencoded
- 解析后的对象放到ctx.request.body中
- 使用koa-multer中间件(第三方库)解析form-data格式
- 注意:解析后的对象不是放到ctx.request.body中而是放到ctx.req.body中
(4)文件上传与express框架类似
- 需要注意的是:文件信息放在ctx.req.file / ctx.req.files
(5)Koa响应内容的方式
- ctx.response.body 与 ctx.body 都可以对客户端做出响应,那么有什么不同呢?
- 观察源码发现 ctx.response.body对ctx.body做了一层代理,执行ctx.body时,实际上是执行ctx.response.body
- 同样在ctx.request.xxx中对ctx.xxx也做了一层代理
- 输出结果的类型
- string、Buffer、Stream、Object||Array、null
(6)静态资源服务器
- 通过koa-static第三方库来实现
- app.use(static(‘./build’))
(7)处理错误的方式
- 通过ctx.app.emit(‘error’,new Error(‘error message’),ctx)发出错误
- 如果app.on(‘error’,(err,ctx)=>{})监听错误
app.use((ctx, next) => {
const isLogin = false;
if (!isLogin) {
ctx.app.emit("error", new Error("您还未登录"), ctx); // 发出错误
}
});
// 监听错误
app.on("error", (err, ctx) => {
ctx.status = 401;
ctx.body = err.message;
});
3.Express与Koa的对比
- 从架构上来说
- express完整且强大,koa整洁且自由可自定义
- express中间件是普通函数且直接执行,koa中间件其实是dispatch,返回的是promise
- koa具有更强的异步处理能力(某个中间件包含异步操作)
- 因为express中间件的回调是普通函数,所以如果要处理异步操作需要手动去写有关异步的逻辑
- 而koa中间件的回调是Promise,可以通过 await next()的方式同步执行异步代码,等异步操作完成后再进行下一步操作