主题
Express实践
一.Express是什么?
Express是一个基于Node平台的web应用开发框架,它提供了一系列的强大特性,帮助你创建各种Web应用
express框架特性:
- 提供了方便简洁的路由定义方式
- 对获取HTTP请求参数进行了简化处理
- 对模板引擎支持程度高,方便渲染动态HTML页面
- 提供了中间件机制有效控制HTTP请求
- 拥有大量第三方中间件对功能进行扩展
二.Express使用步骤
- 1、安装 ---->
npm install express
- 2、引入Express框架----->
const express = require('express')
; - 3、使用框架创建web服务器----->
const app = express()
; - 4、当客户端以get方式访问/路由时
- 5、程序监听端口----->
app.listen(3000)
;
Express框架对路由功能进行了封装,提供了get、post等方法,使得定义路由非常简便。
express响应客户端的API:https://www.expressjs.com.cn/4x/api.html#res.render
三.安装Expess
sh
$ npm install --save express
若要用express来构建一个网站,还得安装一些常用的第三方模块:
sh
# body-parser 用于获取通过post提交数据的,是一个中间件
$ npm install --save body-parser
# cookie-parser 解析Cookie的工具,通过request.cookies可以取到传过来的cookie,并把它们转成对象
$ npm install cookie-parser cookie-parser
# multer 用于处理 multipart/form-data 类型的表单数据,它主要用于上传文件,Multer 不会处理任何非 multipart/form-data 类型的表单数据。
$ npm install --save multer
四.搭建服务器
express搭建服务器很简单
js
const express = require("express");
const app = express();
app.get("/", function (req, res) {
res.send("来自Express服务器的响应");
});
app.listen(3000, function () {
console.log("服务器启动成功,访问地址:http://localhost:3000");
});
五.Express中间件
(一)什么是中间件
- 中间件就是一堆方法,可以想象成高速路之间的收费站
(二)中间件的作用
- 1、可以接收客户端发来的请求
- 2、可以对请求做出响应
- 3、也可以将请求交给下一个中间件继续处理---next( )
(三)中间件的组成
- 1、中间件方法
- 中间件方法由Express提供,负责拦截请求
- 2、请求处理函数
- 请求处理函数由开发人员提供,负责处理请求
中间件函数有三个参数,分别为请求对象req、响应对象res、释放控制权方法next。 中间件函数中的代码执行完成之后需要调用next()方法,才能开始执行下一个中间件,否则请求将挂起。(客户端就得不到响应)
例如:
js
app.get('请求路径', '处理函数',next) // 接收并处理get请求
app.post('请求路径', '处理函数', next) // 接收并处理post请求
注意
- 1、可以针对同一个请求设置多个中间件,对同一个请求进行多次处理
- 2、默认情况下,请求从上到下依次匹配中间件,一旦匹配成功,终止匹配。
- 3、可以调用第三个参数next方法将请求的控制权交给下一个中间件,直到遇到结束请求的中间件,
- 4、中间件的使用是有顺序的,express内是代码的书写顺序,前面的中间件处理过的请求会通过next函数传递到下一个中间件,因此可以在前面的中间件内往req上挂载一些特定参数,在后面中间件都可以取到。
js
// 客户端访问/request路由时,此处的 next会对请求进行拦截,内部处理后,传给下一个中间件
app.get('/request', (req, res, next) => {
req.name = "张三";
next();
});
// 请求到了这里,遇到了send()方法,停止匹配,
app.get('/request', (req, res) => {
res.send(req.name);
});
(四)另一种中间件 app.use中间件
1. 使用app.use()
方法定义中间件
情况一: 当只有中间函数时,能匹配所有的请求方式,此时可以直接传入 请求处理函数,代表接收所有的请求。
js
// app.use( ) 括号内只有中间件函数
app.use((req, res, next) => {
console.log(req.url);
next();
});
情况二: app.use()
第一个参数也可以传入请求地址,代表不论什么请求方式,只要是这个请求地址就接收这个请求。
js
app.use('/admin', (req, res, next) => { // 此处的'/admin是请求地址,只要客户端访问这个请求地址,就会匹配该条路由
console.log(req.url);
next();
//等上面的内部处理结束后,next()会将控制权交给下一个中间件,直到遇到了send()方法(最好加个return,否则请求还会往下走)
});
2.app.use中间件需求场景
- 1、路由保护
- 客户端在访问需要登录的页面时,可以先使用中间件判断用户登录状态,用户如果未登录,则拦截请求,直接响应,禁止用户进入需要登录的页面。(if判断)
- 2、网站维护公告
- 在所有路由的最上面定义接收所有请求的中间件,直接为客户端做出响应,网站正在维护中。(使用全局匹配的,即只有中间件函数的情况)
- 3、自定义404页面
- (请求的资源不存在或未找到)
(五)错误处理的中间件
1、什么是错误处理中间件?
- 错误处理中间件是一个集中处理错误的地方。可以看成是高速出了事故,下高速的一种特殊方式,被带离
2、为什么要使用错误处理中间件?
- 在程序执行的过程中,不可避免的会出现一些无法预料的错误,比如文件读取失败,数据库连接失败
例如:
js
app.use((err, req, res, next) => {
res.status(500).send('服务器发生未知错误');
})
// 当程序出现错误时,调用next()方法,并且将错误信息通过参数的形式传递给next()方法,即可触发错误处理中间件。
app.get("/", (req, res, next) => {
fs.readFile("/file-does-not-exist", (err, data) => {
if (err) {
next(err);
}
});
});
3、捕获错误
在node.js中,异步API的错误信息都是通过回调函数获取的, 支持Promise对象的异步API发生错误可以通过catch方法捕获。
异步函数执行如果发生错误要如何捕获错误呢?
- try catch 可以捕获异步函数以及其他同步代码在执行过程中发生的错误,但是不能其他类型的API发生的错误。
js
app.get("/", async (req, res, next) => {
try {
await User.find({name: '张三'})
}catch(ex) {
next(ex);
}
});
六.Express路由
路由用于确定应用程序如何响应对特定端点的客户机请求,包含一个 URI(或路径)和一个特定的 HTTP 请求方法(GET、POST 等)。 Express内部已经内置了路由模块
js
// 入口文件index.js
const express = require("express");
const app = express();
const router = require("./router");
app.use(router);
app.listen(3000, () => {
console.log("启动成功:http://127.0.0.1:3000");
});
// 路由文件index.js
const express = require('express');
const router = express.Router()
router.get('/',(req,res)=>{
res.send('Router返回的数据')
})
module.exports = router
(一)获取GET请求参数
使用req.query即可获取GET参数,框架内部会将GET参数转换为对象并返回。
(二)获取params参数
通过 req.params
获取动态路由参数
(三)获取post、put、delete请求body中的参数
老版本的Express需要单独下载body-parse并引入才能使用body-parse, 新本版的Express已经内置了此模块,直接通过express调用body-parse的方法即可
注意:
- 无论老版本还是新本版,解析body的中间件必须放在路由中间件之前,否则无法在req上挂载请求的body,路由中也就无法取到req.bodyd的值
js
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
老版本使用方法
js
// 引入body-parser模块
const bodyParser = require('body-parser');
// 配置body-parser模块
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// 接收请求
app.post('/add', (req, res) => {
// 接收请求参数
console.log(req.body);
})
(四)请求对象和响应对象
1、请求对现象request,简称req
获取方式 | 得到信息 |
---|---|
req.query | 一个对象,以键值对存放的查询字符串参数 |
req.params | 一个数组,包含命名过的路由参数 |
req.param(name) | 返回命名的路由参数 |
req.body | 一个对象,请求体 |
req.cookies | 一个对象,包含客服端传来的cookies值 |
req.router | 当前匹配路由信息 |
req.headers | 客服端传过来的请求头 |
req.ip | 客户端的Ip地址 |
req.path | 请求路径 |
req.host / hostname | 返回主机名 |
req.originalUrl | 获取原始请求URL |
req.protocol | 获取协议类型 |
req.accepts() | 检查可接受的请求的文档类型 |
req.get() | 获取指定的HTTP请求头 |
req.is() | 判断请求头Content-Type的MIME类型 |
req.baseUrl | 获取路由当前安装的URL路径 |
2、响应对现象response,简称res
获取方式 | 得到信息 |
---|---|
res.status(code) | 返回HTTP状态码 |
res.cookie(name,vaue,[options]) | 设置客户端 opition: domain / expires / httpOnly / maxAge / path / secure / signed |
res.clearCookie(name,[options]) | 清除客户端cookie |
res.send(body) 或 res.send(status,body) | 向客户端发送响应及可选的状态码 |
res.json(json) 或 res.json(status,json) | 向客户端发送json以及可选的状态码 |
res.redirect([status],url) | 重定向浏览器 |
res.download() | 传送指定路径的文件 |
res.jsonp() | 传送JSONP响应 |
res.render(view,[locals],callback) | 渲染一个view,同时向callback传递渲染后的字符串,如果在渲染过程中有错误发生next(err)将会被自动调用。callback将会被传入一个可能发生的错误以及渲染后的页面,这样就不会自动输出了。 |
res.sendFile(path [,options] [,fn]) | 传送指定路径的文件 -会自动根据文件extension设定Content-Type |
res.set() | 设置HTTP头,传入object可以一次设置多个头 |
res.type() | 设置Content-Type的MIME类型 |
res.append() | 追加指定HTTP头 |
七.Express跨域
(一)引发跨域因素
协议、域名、端口三者之一不同,就会引发跨域,如下图:
(二)解决:
1、cors解决跨域
js
app.all("*", function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*"); //设置允许跨域的域名,*代表允许任意域名跨域
res.header("Access-Control-Allow-Headers", "content-type"); //允许的header类型
res.header("Access-Control-Allow-Credentials", true); //接收ajax请求手动提交的cookie信息
res.header("Access-Control-Allow-Methods", "DELETE,PUT,POST,GET,OPTIONS"); //跨域允许的请求方式
if (req.method == 'OPTIONS'){
res.sendStatus(200); //让options尝试请求快速结束
}else{
next();
}
});
注意:以上的跨域中间件需要设置在router之前
2、cors第三方跨域中间件解决跨域
1、下载cors跨域中间件模块
sh
$ npm i cors -S
2、配置cors中间件
js
const cors = require("cors");
app.use(cors({
origin: "http://localhost:3000",
credentials: true,
maxAge: "60" //缓存option请求结果60秒
}));
更多跨域解决方案请查看另一篇文章常见跨域及解决方案
八.Express文件上传与下载
文件的上传与下载在一个web服务中很常见,在nodejs中,提供fs模块来处理文件
(一)文件上传
1、前端
前端上传文件一般需要借助FormData,且要设置 Content-Type
为 multipart/form-data
js
let file = ....
let formData = new FormData()
formData.append('file',)
2. Express服务端
Express上传文件需要借助multer第三方中间件模块 github地址为 https://github.com/expressjs/multer/blob/master/doc/README-zh-cn.md 其他插件还有 Formidable :https://github.com/node-formidable/formidable
Formidable
:用于解析表单数据的 Node.js 模块,尤其是文件上传
这里以 multer 为例
文件上传基本步骤为(这里以 multer 为例):
- 下载 multer, 引入 multer 和 fs 模块
- 配置multer中间件
- 用fs模块,将文件保存到指定目录
(1)、下载 multer, 引入 multer 和 fs 模块
- multer: 中间件用于处理 multipart/form-data 类型的表单数据,它主要用于上传文件
- fs:模块用于的文件操作
(2)、配置multer中间件
- Multer 会添加一个 body 对象 以及 file 或 files(批量上传时为files) 对象 到 express 的 request 对象中。 body 对象包含表单的文本域信息,file 或 files 对象包含对象表单上传的文件信息。
js
app.use(multer({ dest: "/upload" })) // dest为文件存储到哪里
// 更多multer信息移步官方文档:https://github.com/expressjs/multer/blob/master/doc/README-zh-cn.md
Multer 的一些说明: 注意: Multer 不会处理任何非 multipart/form-data 类型的表单数据。
(3)、用fs模块读取上传的文件,将文件保存到磁盘指定目录
js
//单个文件上传
router.post("/single", upload.single("file"), (req, res) => {
let result;
let des_file =
path.resolve(".") + "/upload/" + Date.now() + "-" + req.file.originalname; //放置上传文件的目录
fs.readFile(req.file.path, function(err, data) {
//从内存中读取文件
fs.writeFile(des_file, data, function(err) {
//读取成功后就将文件写入到磁盘
if (err) {
result = {
message: "File uploaded fail",
beacuse: err.message,
filename: req.file.originalname
};
} else {
result = {
message: "File uploaded successfully",
filename: req.file.originalname
};
}
res.send(result);
});
});
});
也可以借助stream的形式
js
router.post("/single", upload.single("file"), (req, res) => {
let des_file =
path.resolve(".") + "/upload/" + Date.now() + "-" + req.file.originalname; //放置上传文件的目录
try {
let readF = fs.createReadStream(req.file.path);
let writeF = fs.createWriteStream(des_file);
readF.pipe(writeF);
res.send({
message: "File uploaded fail",
beacuse: err.message,
filename: req.file.originalname
});
} catch (error) {
req.send({
message: "File uploaded successfully",
filename: req.file.originalname
});
}
});
完整示例
js
const express = require("express");
const fs = require("fs");
const multer = require("multer");
const app = express();
const router = express.Router();
const path = require("path");
const cors = require("cors");
app.use(
cors({
origin: "*",
credentials: true,
maxAge: "60" //缓存option请求结果60秒
})
);
app.use(express.static("./public"));
app.use(express.urlencoded({ extends: false }));
app.use(express.json());
// .single("file") file 和.array('files') files 为字段名称,前后端协商一致即可
const upload = multer({ dest: "/tmp/" });
router.post("/single", upload.single("file"), (req, res) => {
console.log("--------------单文件信息---------\n", req.file);
// console.log("--------------多文件信息数组---------\n",req.files); // 多文件上传时候
console.log(
"--------------其他参数---------\n",
JSON.stringify(req.body, null, 4)
);
if (!req.file) {
res.send("无效文件");
return;
}
try {
let des_file =
path.resolve(".") + "/public/" + Date.now() + "-" + req.file.originalname; //放置上传文件的目录
// // 不适用stream的形式
// fs.readFile(req.file.path, function(err, data) {
// //从内存中读取文件
// fs.writeFile(des_file, data, function(err) {
// //读取成功后就将文件写入到磁盘
// if (err) {
// result = {
// message: "File uploaded fail",
// beacuse: err.message,
// filename: req.file.originalname
// };
// } else {
// result = {
// message: "File uploaded successfully",
// filename: req.file.originalname
// };
// }
// res.send(result);
// });
// });
// 将文件写入磁盘
fs.createReadStream(req.file.path).pipe(fs.createWriteStream(des_file));
res.send(req.body);
} catch (error) {
throw error;
}
});
app.use(router);
app.use((err, req, res, next) => {
res.status(500).send("服务器发生未知错误");
});
// 当程序出现错误时,调用next()方法,并且将错误信息通过参数的形式传递给next()方法,即可触发错误处理中间件。
app.get("/", (req, res, next) => {
fs.readFile("/file-does-not-exist", (err, data) => {
if (err) {
next(err);
}
});
});
app.listen(3000, () => {
console.log("服务器启动成功:http://localhost:3000");
});
(二)文件下载
下载文件的方式有很多种,常见的有二进制流,base64码,直接返回文件url地址;若是下载excel, 可参考插件excel.js
可参考这篇文章Node.js 中实现HTTP文件下载
blob下载思路 1、客户端发起请求 2、服务器查找对应文件,并设置Content-Type(表示文件的MIME类型)、Content-Disposition(用于及文件描述)等响应头,常见响应头 3、客户端根据服务器返回的响应头解析和接收文件数据
实现
js
router.get('/file/:fileName', function(req, res, next) {
// 取到要下载的文件标识
var fileName = req.params.fileName;
// 找到文件
var filePath = path.join(__dirname, fileName);
// 获取对应路径下文件信息
var stats = fs.statSync(filePath);
if(stats.isFile()){ // 判断是否是文件
// 设置响应头
res.set({
'Content-Type': 'application/octet-stream',
'Content-Disposition': 'attachment; filename='+fileName,
'Content-Length': stats.size
});
// 文件下载
fs.createReadStream(filePath).pipe(res);
} else {
res.end(404);
}
});
九.Express托管静态资源
Express内置的express.static中间件可以方便的托管静态资源文件,例如:图片,css,javascript文件,等 express.static()接受一个文件夹路径作为参数,推荐绝对路径 使用
js
const express = require('express');
const path = require('path');
const app = express()
app.listen(3000)
app.use(express.static(path.join(__dirname,'public')))
访问: http://127.0.0.1:3000就是访问的public这层目录,后面直接跟public里面的资源路径即可 例如:
- http://127.0.0.1:3000/index.html
- http://127.0.0.1:3000/images/123.png
- http://127.0.0.1:3000/js/index.js
十.Express脚手架
就好像vue-cli,create-react-app一样,express也有自己的脚手架。 通过应用生成器工具 express-generator 可以快速创建一个应用的骨架。
(一)生成项目骨架
方案1、可以通过 npx (包含在 Node.js 8.2.0 及更高版本中)命令来运行 Express 应用程序生成器。
sh
$ npx express-generator
方案2、对于较老的 Node 版本,请通过 npm 将 Express 应用程序生成器安装到全局环境中并执行即可。
sh
$ npm install -g express-generator
$ express
注意:
- express命令可以带参数,参数如下:
-h
参数可以列出所有可用的命令行参数:
sh
$ express -h
Usage: express [options] [dir]
Options:
-h, --help 输出使用方法
--version 输出版本号
-e, --ejs 添加对 ejs 模板引擎的支持
--hbs 添加对 handlebars 模板引擎的支持
--pug 添加对 pug 模板引擎的支持
-H, --hogan 添加对 hogan.js 模板引擎的支持
--no-view 创建不带视图引擎的项目
-v, --view <engine> 添加对视图引擎(view) <engine> 的支持 (ejs|hbs|hjs|jade|pug|twig|vash) (默认是 jade 模板引擎)
-c, --css <engine> 添加样式表引擎 <engine> 的支持 (less|stylus|compass|sass) (默认是普通的 css 文件)
--git 添加 .gitignore
-f, --force 强制在非空目录下创建
例如,如下命令创建了一个名称为 myapp 的 Express 应用。此应用将在当前目录下的 myapp 目录中创建,并且设置为使用 Pug 模板引擎(view engine):
sh
$ express --view=pug myapp
create : myapp
create : myapp/package.json
create : myapp/app.js
create : myapp/public
create : myapp/public/javascripts
create : myapp/public/images
create : myapp/routes
create : myapp/routes/index.js
create : myapp/routes/users.js
create : myapp/public/stylesheets
create : myapp/public/stylesheets/style.css
create : myapp/views
create : myapp/views/index.pug
create : myapp/views/layout.pug
create : myapp/views/error.pug
create : myapp/bin
create : myapp/bin/www
(二)安装项目依赖
sh
$ cd myapp
$ npm install
(三)启动项目
在 MacOS 或 Linux 中,通过如下命令启动此应用:
sh
$ DEBUG=myapp:* npm start
在 Windows 命令行中,使用如下命令:
sh
> set DEBUG=myapp:* & npm start
在 Windows 的 PowerShell 中,使用如下命令:
sh
PS> $env:DEBUG='myapp:*'; npm start
然后在浏览器中打开 http://localhost:3000/
网址就可以看到这个应用了。
通过生成器创建的应用一般都有如下目录结构:可参考文章:《express 项目文件目录说明及功能描述》
.
├── app.js // 这是项目入口文件,很重要
├── bin
│ └── www // 这是服务端配置文件也就是项目启动脚本可执行文件,管理的是使用 url 访问项目的端口号,
以及url访问出错情况下的一些处理
├── package.json
├── public // 这是放置静态文件的地方,如果不使用后端传输图片或文件就不用管
│ ├── images
│ ├── javascripts
│ └── stylesheets
│ └── style.css
├── routes // 路由目录
│ ├── index.js
│ └── users.js
└── views // 页面模板目录
├── error.pug
├── index.pug
└── layout.pug
7 directories, 9 files