前言 这篇会介绍使用express的基本使用
为什么要使用express 因为http模块来开发,有一些问题
在实际开发中,我们要根据不同请求路径,不同请求方法做不同的事情,处理起来比较麻烦
读取请求体和写入响应体是通过流的方式,使用起来比较麻烦
所以我们一般会使用一些框架,常用的有
这里只介绍express
官方文档:https://expressjs.com/
中文网:https://www.expressjs.com.cn/
express的基本使用 首先,你要安装一下express
1 npm install express --save
添加下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 const express = require ("express" );const app = express();const port = 5555 ;app.get("/data/:id" , (req, res ) => { console .log("请求头" , req.headers); console .log("请求路径" , req.path); console .log("query" , req.query); console .log("params" , req.params); res.setHeader("a" , "123" ); res.setHeader("b" , "456" ); res.send({ name : "SakuraSnow" }); }); app.get("*" , (req, res ) => { console .log("abc" ); res.send({ name : "Snow" }) }); app.listen(port, () => { console .log(`server listen on ${port} ` ); });
要创建一个简单的服务器,你只需要做下面几件事
创建一个express应用
配置请求映射
开启服务器
是不是很简单呢
express中间件 如果你用过redux,那你一定对中间件不陌生(虽然我已经把react忘得差不多了),中间件本质上就是一个函数,一般来说用于给系统添加某些功能(比如添加CORS)或者处理某些数据
中间件有下面的特点
按顺序执行;
可执行任何脚本;
可以对request对象和response对象进行overwrite;
可以响应请求以结束本次请求生命周期;
通过next方法执行下一个中间件;
我们一般使用use表示使用中间件,和get等不同的是,它设置的字段,路径只要以它开头就行,比get和post等更广
比如使用app.use(“/data”),可以匹配到的地址包括”/data”,”/data/a”, “/data/b”
下面是简单的中间件使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 const express = require ("express" );const app = express();const port = 5555 ;app.get("/data/:id" , (req, res, next ) => { console .log("handler0" ) console .log("请求头" , req.headers); console .log("请求路径" , req.path); console .log("query" , req.query); console .log("params" , req.params); next() }); app.get("/data/:id" , (req, res, next ) => { console .log("handler1" ) res.setHeader("a" , "123" ); next() }) app.get("/data/:id" , (req, res, next ) => { console .log("handler2" ) res.setHeader("b" , "456" ); next() }); app.get("/data/:id" , (req, res, next ) => { console .log("handler3" ) res.send({ name : "SakuraSnow" }); }) app.get("*" , (req, res ) => { console .log("abc" ); res.send({ name : "Snow" }) }); app.listen(port, () => { console .log(`server listen on ${port} ` ); });
用postman访问,服务器会打印下面的日志
可以看出,中间件会按绑定的顺序执行
另外,你可以在中间件中抛出错误(或者真的有预料之外的错误)
express就会寻找后续的错误处理中间件,把控制权转交给错误处理中间件
如果没有,服务器就会响应500
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 const express = require ("express" );const app = express();const port = 5555 ;app.get("/data/:id" , (req, res, next ) => { console .log("handler0" ) console .log("请求头" , req.headers); console .log("请求路径" , req.path); console .log("query" , req.query); console .log("params" , req.params); res.send({ name : "SakuraSnow" }); }); app.get("/error" , (req, res, next ) => { throw new Error ("something wrong" ) }) app.use('*' , (req, res ) => { res.status(404 ).send({ code: 404 , success: false , data: '' , msg: 'the directory you request is not exist in the server' }) }) app.use("*" , (err, req, res, next ) => { res.status(500 ).send({ code: 500 , success: false , data: '' , msg: err.toString() }) }) app.listen(port, () => { console .log(`server listen on ${port} ` ); });
express常用中间件 静态资源中间件express.static 使用方法很简单,加上下面的代码即可
1 2 3 4 5 6 7 8 9 const path = require ("path" );const staticRoot = path.resolve(__dirname, "./public" );app.use("/static" , express.static(staticRoot));
访问http://localhost:5555/static/index.html,就可以看到页面展示出来了
express.urlencoded可以解析格式为application/x-www-form-urlencoded的数据并放入到req.body中
假如有下面的代码
1 2 3 4 5 6 7 8 app.post("/data/:id" , (req, res, next ) => { console .log("query" , req.query); console .log("params" , req.params); console .log("body" , req.body); res.send({ name : "Snow" }) })
在postman中,我们对这个接口进行测试
在服务器控制台中打印数据,可以看到下面的结果
1 2 3 query {} params { id: '1' } body undefined
这里的body是undefined,是不是意味着请求失败了呢,其实不是,因为我们有时候服务器的请求体里会带上一个文件,所以我们要用流的方式来解析请求体,这里express没有给我们进行处理,但是大多数情况下,我们都不会在post请求里发文件,所以,我们有时更希望能直接解析消息体,这时候就要用到我们的express.urlencoded中间件了
使用方法也很简单(这个extended选项用于标志内部使用qs库来解析消息体)
1 app.use(express.urlencoded({ extended : true }));
加入后,再次请求
1 2 3 query {} params { id: '1' } body { data: '123' }
这样就可以直接解析了
事实上,它的工作原理大概就是这样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const qs = require ("querystring" );module .exports = (req, res, next ) => { if (req.headers["content-type" ] === "application/x-www-form-urlencoded" ) { let result = "" ; req.on("data" , (chunk ) => { result += chunk.toString("utf-8" ); }); req.on("end" , () => { const query = qs.parse(result); req.body = query; next(); }); } else { next(); } };
解析application/json格式的数据
express.json可以解析application/json的数据并放入到req.body中
但有时我们传递的消息体是这样的
这时我们就要使用另外一个中间件express.json了
使用方法还是很简单
1 app.use(express.json());
最后放个完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 const express = require ("express" );const path = require ("path" );const app = express();const port = 5555 ;const staticRoot = path.resolve(__dirname, "./public" );app.use("/static" , express.static(staticRoot)); app.use(express.urlencoded({ extended : true })); app.use(express.json()); app.get("/data/:id" , (req, res, next ) => { console .log("请求头" , req.headers); console .log("请求路径" , req.path); console .log("query" , req.query); console .log("params" , req.params); res.send({ name : "SakuraSnow" }); }); app.post("/data/:id" , (req, res, next ) => { console .log("head" , req.headers) console .log("query" , req.query); console .log("params" , req.params); console .log("body" , req.body); res.send({ name : "Snow" }) }) app.get("/error" , (req, res, next ) => { throw new Error ("something wrong" ) }) app.use('*' , (req, res ) => { res.status(404 ).send({ code: 404 , success: false , data: '' , msg: 'the directory you request is not exist in the server' }) }) app.use("*" , (err, req, res, next ) => { res.status(500 ).send({ code: 500 , success: false , data: '' , msg: err.toString() }) }) app.listen(port, () => { console .log(`server listen on ${port} ` ); });
Multer 用于解析multipart/form-data
文档看这里:https://github.com/expressjs/multer
Multer 会添加一个 body
对象 以及 file
或 files
对象 到 express 的 request
对象中。 body
对象包含表单的文本域信息,file
或 files
对象包含对象表单上传的文件信息。
安装
1 cnpm install multer --save
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 const express = require ("express" );const router = express.Router();const multer = require ("multer" );const path = require ("path" );const storage = multer.diskStorage({ destination: function (req, file, cb ) { cb(null , path.resolve(__dirname, "../../public/upload" )); }, filename: function (req, file, cb ) { const timeStamp = Date .now(); const randomStr = Math .random().toString(36 ).slice(-6 ); const ext = path.extname(file.originalname); const filename = `${timeStamp} -${randomStr} ${ext} ` ; cb(null , filename); }, }); const upload = multer({ storage, limits: { fileSize: 1024 * 1024 , }, fileFilter (req, file, cb ) { const extname = path.extname(file.originalname); const whitelist = [".jpg" , ".gif" , ".png" ]; if (whitelist.includes(extname)) { cb(null , true ); } else { cb(new Error (`your ext name of ${extname} is not support` )); } }, }); router.post("/uploadFile" , upload.single("img" ), (req, res ) => { console .log("body" , req.body); console .log("file" , req.file); const url = `/static/upload/${req.file.filename} ` ; res.send({ code: 0 , msg: "" , src: url, }); }); module .exports = router;
在index.js中导入
1 app.use("/" , require ("./api/upload" ));
写个页面测试下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" /> <meta name ="viewport" content ="width=device-width, initial-scale=1.0" /> <title > Document</title > <link rel ="stylesheet" href ="./css/index.css" /> <script src ="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.js" defer > </script > </head > <body > <input type ="file" id ="fileInput" /> <button class ="button" > 上传</button > <img src ="" /> <script > let fileInput = document .querySelector("#fileInput" ); let button = document .querySelector(".button" ); let img = document .querySelector("img" ); button.addEventListener("click" , async () => { let formData = new FormData(); formData.append("img" , fileInput.files[0 ]); let data = await axios.post("http://localhost:5555/uploadFile" , formData); img.src = data.data.src; }) </script > </body > </html >
测试成功
express路由 我们可以使用路由对接口进行管理,而不是用代码堆在一起
就像下面的代码
1 2 3 4 5 app.use("/api/student" , require ("./api/student" ));
然后我们可以把学生相关的api都提取起来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 const express = require ("express" );const router = express.Router();router.get( "/:id" , (req, res) => { } ); router.delete( "/:id" , (req, res) => { } ); router.delete( "/:id" , (req, res) => { } ); module .exports = router;
这样收集起来,就可以很方便管理
文件下载 其实非常简单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const express = require ("express" );const path = require ("path" );const router = express.Router();router.get("/:filename" , (req, res ) => { const absolutePath = path.resolve( __dirname, "../../resources" , req.params.filename ); res.download(absolutePath, req.params.filename); }); module .exports = router;
1 2 app.use("/download" , require ("./api/download" ));
然后访问浏览器:http://localhost:5555/download/head.jpg
浏览器就可以进行下载了
CORS 啊哈,可以康康我这篇文章 ,好久前写过了,这里就不复制粘贴了
Cookie Cookie是身份认证的重要一环,在这里我们使用一个中间件cookie-parse
来设置cookie,
安装
1 npm install cookie-parser --save
安装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const cookieParser = require ("cookie-parser" );app.use(cookieParser()); app.get("/data/:id" , (req, res, next ) => { console .log("cookie" , req.cookies); res.cookie("token" , "abc123" , { path: "/" , domain: "localhost" , maxAge: 7 * 24 * 3600 * 1000 , }); res.send({ name : "SakuraSnow" }); });
如果你想了解cookie的更多信息,可以参见另一篇文章:
Session Session一般也用来身份认证,不过它和Cookie有略微的区别
这里简单介绍下Session的技术原理和优缺点
先介绍下Session的大致原理,先放点祖传图
大致流程是这样的
服务器创建一个表,记录sessionId和数据对象的映射关系
客户端登录后,服务器生成一个新的sessionId(一般会进行加密处理)并返回给客户端
客户端保存这个sessionId(一般是存在Cookie里)
客户端之后的每次请求,都带上这个sessionId
服务器接收到sessionId后进行解析,拿到sessionId对应的数据对象
然后我们介绍下Session的优缺点
先看看Cookie有什么特点吧
存储在客户端
优点
缺点
只能是字符串格式
存储量有限
数据不安全,容易被获取,篡改
数据容易丢失
每个请求都会带在HTTP请求头中,影响性能
要是用户端禁用Cookie,就会失效
然后我们再介绍下Session的特点
真正的数据存储在服务端,对客户端不可见
优点
可以是任何格式
存储量理论上是无限的(只要内存或者硬盘够的话)
数据比较安全,难以获取和篡改
数据不容易丢失
请求时不会带上很长的信息
在用户端禁用Cookie的情况下还是可以使用(使用URL重写)
JWT JWT嘛,和前面两个不大一样,它的全程叫Json Web Token
,非要翻译的话,应该是JSON格式的互联网令牌。嘛,翻译起来怪怪的,感觉少了点内味
暂且不提这个,JWT就是一个安全可靠的令牌格式
以此,你可以把它存储到任何地方,无论是cookie还是localstorage,如果客户端不是浏览器,而是桌面端应用,也可以把它存在文本中
对于传输,你可以使用任何传输方式来传输JWT,一般来说,我们会使用消息头来传输它
大致过程如下
服务器给客户端响应一个JWT令牌
客户端自行存储JWT令牌
在网络请求时,按照约定的方式带上JWT令牌
服务器获取令牌并解析
JWT令牌由三个部分组成,分别是:
header:令牌头部,记录了整个令牌的类型和签名算法
payload:令牌负荷,记录了保存的主体信息,比如你要保存的用户信息就可以放到这里
signature:令牌签名,按照头部固定的签名算法对整个令牌进行签名,该签名的作用是:保证令牌不被伪造和篡改
它们组合而成的完整格式是:header.payload.signature
比如,一个完整的jwt令牌如下:
1 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE1ODc1NDgyMTV9.BCwUy3jnUQ_E6TqCayc7rCHkx-vxxdagUwPOWqwYCFc
它各个部分的值分别是:
header:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
payload:eyJmb28iOiJiYXIiLCJpYXQiOjE1ODc1NDgyMTV9
signature: BCwUy3jnUQ_E6TqCayc7rCHkx-vxxdagUwPOWqwYCFc
另外,服务器可以验证令牌是否被篡改
验证方式非常简单,就是对header+payload
用同样的秘钥和加密算法进行重新加密
然后把加密的结果和传入JWT的signature
进行对比,如果完全相同,则表示前面两部分没有动过,就是自己颁发的,如果不同,肯定是被篡改过了。
所以,总结一下JWT的特点
JWT本质上是一种令牌格式。它和终端设备无关,同样和服务器无关,甚至与如何传输无关,它只是规范了令牌的格式而已
JWT由三部分组成:header、payload、signature。主体信息在payload
JWT难以被篡改和伪造。这是因为有第三部分的签名存在。
后记 不知道写啥,祝您身体健康)