环境
- Ubuntu 22.04
- Node.js 16.15.1
- VSCode 1.69.2
- Firefox 103.0
概述
Express官网对Express的定义是:基于Node.js平台,快速、开放、极简的Web开发框架。
Express是npm上的第三方包,作用和Node.js内置的 http
模块类似,用来创建Web服务器。
安装方法为: npm i express
简单示例
创建文件 0727_9.js
如下:
const express = require('express');
const app = express();
app.get('/index.html', (req, res) => {
res.send('Success!');
});
app.listen(8080, () => {
console.log('server started');
});
运行程序,访问服务器:
可以用 req.query
获取查询参数(比如 ?name=Tom&age=20
):
app.get('/a.html', (req, res) => {
res.send(req.query);
});
效果如下:
可以用 req.params
获取请求的路径参数:
app.get('/:xxx/b.html', (req, res) => {
res.send(req.params);
});
效果如下:
托管静态资源
使用 express.static()
方法,可以很方便的创建一个静态资源服务器。
我们之前通过把MarkDown文档转换为HTML文档,生成了 preview
目录,其中包含了 test.html
文件,和一些css以及js文件。如下:
创建文件 0727_10.js
如下:
const express = require('express');
const app = express();
app.use(express.static('preview'));
app.listen(8080, () => {
console.log('server started');
});
效果如下:
也可以定制化访问的URL路径,例如:
app.use('/bbb', express.static('preview'));
则访问时,也需要加上 /bbb
前缀,即: http://localhost:8080/bbb/test.html
。
Express路由
前面的例子里,我们使用了多个 app.get()
方法,对应不同的请求处理。事实上Express不建议将路由直接挂载到app上,而是推荐将路由抽离为单独的模块。使用 express.Router()
方法创建路由对象,通过 module.exports()
导出路由对象,在外面使用 app.use()
方法注册路由模块。
创建文件 0727_11.js
:
const express = require('express');
const router = express.Router();
router.get('/users', (req, res) => {
res.send('get users');
});
router.get('/books', (req, res) => {
res.send('get books');
});
module.exports = router;
创建文件 0727_12.js
:
const express = require('express');
const myRouter = require('./0727_11.js');
const app = express();
app.use(myRouter);
app.listen(8080, () => {
console.log('server started');
});
效果如下:
同样,也可以定制化访问的URL路径,例如:
app.use('/bbb', myRouter);
则访问时,也需要加上 /bbb
前缀,即: http://localhost:8080/bbb/users
。
Express中间件
可以在处理请求之前,先做一个或者多个预处理。
注意:一定要在路由之前注册中间件。
全局中间件
全局中间件适用于所有请求。
用法为:
app.use((req, res, next) => {
......
next();
});
创建文件 0727_13.js
:
const express = require('express');
const app = express();
app.use((req, res, next) => {
console.log(req.url + ': 1st middleware');
next();
});
app.use((req, res, next) => {
console.log(req.url + ': 2nd middleware');
next();
});
app.use((req, res, next) => {
console.log(req.url + ': 3rd middleware');
next();
});
app.get('/users', (req, res) => {
res.send('get users');
});
app.get('/books', (req, res) => {
res.send('get books');
});
app.listen(8080, () => {
console.log('server started');
});
当访问 http://localhost:8080/users
时,输出如下:
/users: 1st middleware
/users: 2nd middleware
/users: 3rd middleware
当访问 http://localhost:8080/books
时,输出如下:
/books: 1st middleware
/books: 2nd middleware
/books: 3rd middleware
可见:
- 全局中间件适用于所有请求;
- 可定义多个全局中间件,按其定义顺序执行;
局部中间件
全局中间件针对所有的请求,如果想要只针对指定的请求,可以使用局部中间件。
创建文件 0727_13.js
:
const express = require('express');
const app = express();
const mw1 = (req, res, next) => {
console.log(req.url + ': 1st middleware');
next();
};
const mw2 = (req, res, next) => {
console.log(req.url + ': 2nd middleware');
next();
};
const mw3 = (req, res, next) => {
console.log(req.url + ': 3rd middleware');
next();
};
app.get('/users', [mw1, mw2], (req, res) => {
res.send('get users');
});
app.get('/books', [mw2, mw3], (req, res) => {
res.send('get books');
});
app.listen(8080, () => {
console.log('server started');
})
本例中,给 /users
请求绑定了 mw1
和 mw2
这两个中间件,给 /books
请求绑定了 mw2
和mw3
这两个中间件。
当访问 http://localhost:8080/users
时,输出如下:
/users: 1st middleware
/users: 2nd middleware
当访问 http://localhost:8080/books
时,输出如下:
/books: 2nd middleware
/books: 3rd middleware
可见,只有绑定的中间件才会生效。
中间件的分类
- 应用级别的中间件
- 路由级别的中间件
- 错误级别的中间件
- Express内置的中间件
- 第三方中间件
应用级别的中间件
通过 app.use()
、 app.get()
、 app.post()
绑定到 app
实例上的中间件。
前面的例子,都是应用级别的中间件。
路由级别的中间件
绑定到 express.Router()
上的中间件,其用法和应用级别中间件类似。
创建文件 0727_15.js
如下:
const express = require('express')
const app = express();
const router = express.Router();
router.use((req, res, next) => {
console.log(req.url + ': 1st router');
next();
});
router.use((req, res, next) => {
console.log(req.url + ': 2nd router');
next();
});
router.get('/users', (req, res) => {
res.send('get users');
});
router.get('/books', (req, res) => {
res.send('get books');
});
app.use(router);
app.listen(8080, () => {
console.log('server started');
});
当访问 http://localhost:8080/users
时,输出如下:
/users: 1st router
/users: 2nd router
当访问 http://localhost:8080/books
时,输出如下:
/books: 1st router
/books: 2nd router
错误级别的中间件
注意:需要注册在所有路由之后,这是和其它中间件不同的。
创建文件 0727_16.js
如下:
const express = require('express');
const app = express();
app.get('/', (req, res) => {
console.log('get it');
throw new Error('Something is wrong!');
res.send('Can you get it?');
});
app.use((err, req, res, next) => {
console.log('Error occurred');
console.log(err.message);
res.send('Sorry but an error occurred');
});
app.listen(8080, () => {
console.log('server started');
});
运行程序,访问 http://localhost:8080
,如下:
输出如下:
get it
Error occurred
Something is wrong!
Express 内置的中间件
比如 express.static()
,在前面提到过。
第三方的中间件
需要显式安装的三方包。
跨域请求
创建文件 0727_17.js
如下:
const express = require('express');
//const cors = require('cors');
const app = express();
//app.use(cors());
app.get('/users', (req, res) => {
res.send('get users');
});
app.listen(8080, () => {
console.log('server started');
});
效果如下:
创建文件 0727_17.html
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="lib/jquery-3.6.0.min.js"></script>
</head>
<body>
<button id="btn1">OK</button>
<script>
$('#btn1').on('click', function () {
$.get('http://localhost:8080/users', function (res) {
console.log(res);
});
});
</script>
</body>
</html>
点击 OK
按钮会报错:
报错原因:这是一个跨域请求,因为二者的协议不同( file
VS. http
)。
解决办法有 CORS
和 JSONP
。
CORS
首先要安装 cors
:
➜ temp0727 npm i cors
added 2 packages, and audited 60 packages in 2s
7 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
在 0727_17.js
文件中添加两行代码(参考注释的代码):
const cors = require('cors');
app.use(cors());
注意要放在 app.get()
前面。
重启服务,现在点击 OK
按钮就不报错了:
查看Response Headers,如下:
可见,多了 Access-Control-Allow-Origin: *
。
注意:浏览器貌似有缓存,如果此时删掉那两行代码,然后重启服务,刷新浏览器,点击 OK
按钮,仍然能成功。只有关闭并重新打开浏览器,点击 OK
按钮才会失败。
客户端在请求cors接口时,可以将cors的请求分为两大类:
- 简单请求
- 预检请求
同时满足以下两大条件的请求,就属于简单请求:
- 请求方式:
GET
或者POST
或者HEAD
; - 头部信息:
- 无自定义头部字段
- Accept
- Accept-Language
- Content-Language
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
- Content-Type(只有三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)
其它请求则属于预检请求。
对于预检请求,浏览器会先发送一个 OPTIONS
请求进行预检,以获知服务器是否允许该实际请求,服务器成功响应预检请求后,才会发送真正的请求。
创建文件0727_18.js
如下:
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
app.delete('/users', (req, res) => {
res.send('delete users');
});
app.listen(8080, () => {
console.log('server started');
});
创建文件 0727_18.html
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="lib/jquery-3.6.0.min.js"></script>
</head>
<body>
<button id="btn1">OK</button>
<script>
$('#btn1').on('click', function () {
$.ajax({
type: 'DELETE',
url: 'http://localhost:8080/users',
success: function (res) {
console.log(res);
}
});
});
</script>
</body>
</html>
效果如下:
可见,对于 DELETE
请求,浏览器先发送了一个 OPTIONS
请求,然后才发送真正的 DELETE
请求。
JSONP
创建文件 0727_19.js
如下:
const express = require('express');
const app = express();
app.get('/jsonp', (req, res) => {
const funcName = req.query.callback;
const data = {name: 'Tom', age: 20};
const str = `${funcName}(${JSON.stringify(data)})`;
res.send(str);
})
app.listen(8080, () => {
console.log('server started');
});
创建文件 0727_19.html
如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="lib/jquery-3.6.0.min.js"></script>
</head>
<body>
<button id="btn1">OK</button>
<script>
$('#btn1').on('click', function () {
$.ajax({
type: 'GET',
url: 'http://localhost:8080/jsonp',
dataType: 'jsonp',
success: function (res) {
console.log(res);
}
});
});
</script>
</body>
</html>
效果如下: