这篇笔记我们介绍Express如何处理各种类型的HTTP请求,以及如何输出HTTP响应。
前面有关路由的代码中,其实我们一直在使用处理函数。Express框架的处理函数支持3个参数:req
、res
、next
,前两个参数分别封装了请求上下文和响应对象。
router.get('/', (req, res, next) => {
res.status(200).send('Match /');
});
next
比较特别,它也是一个函数,调用next()
表示将请求交给下一个合适的处理函数进行处理。实际上,Express框架支持为一个路由配置多个处理函数,如果前面的处理函数匹配了请求,但它处理完成后还不能返回,而是需要将请求继续交给下一个处理函数或中间件进行处理,此时就会用到next()
。下面代码是一个例子。
const express = require('express');
const router = express.Router();
const app = express();
const func1 = (req, res, next) => {
console.log('func1');
next();
};
const func2 = (req, res, next) => {
console.log('func2');
next();
};
const func3 = (req, res, next) => {
console.log('func3');
res.status(200).send('Hello, world!');
};
router.get('/', [func1, func2, func3]);
app.use(router);
app.listen(8080, () => {
console.log('Express server listen on 8080');
});
我们可以使用req.query
读取URL参数。
router.get('/student', (req, res, next) => {
const id = req.query.id;
res.send('Student ID [' + id + ']');
});
上面例子代码中,我们读取了id
这个URL参数。
对于Restful路径参数,前面介绍过可以使用类似:id
的形式定义路由,取参数时我们可以使用req.params
。
router.get('/student/:id', (req, res, next) => {
const id = req.params.id;
res.send('Student ID [' + id + ']');
});
读取JSON请求体需要使用Express框架提供的express.json()
中间件,它能识别application/json
请求并自动解析请求体。
app.use(express.json());
我们使用app.use()
设置了express.json()
中间件,注意它需要设置在router
中间件之前。
router.post('/student', (req, res, next) => {
const student = req.body;
res.send('Student [' + JSON.stringify(student) + ']');
});
具体的请求处理代码中,我们创建了一个匹配POST /student
的路由,处理函数中使用req.body
获取了请求体对象。
我们知道表单有x-www-form-urlencoded
和form-data
两种格式,Express内置了对于前者的支持,而后者需要安装扩展来支持。
对于x-www-form-urlencoded
格式表单我们需要设置一个express.urlencoded()
中间件,它会自动识别application/x-www-form-urlencoded
类型的表单请求,并自动解析处理。
app.use(express.urlencoded());
具体的处理函数和前面处理JSON请求体时是完全一样的。
router.post('/student', (req, res, next) => {
const student = req.body;
res.send('Student [' + JSON.stringify(student) + ']');
});
form-data
格式既可以用于文本表单也可以用于文件上传,然而Express没有内置form-data
格式表单的处理,不过有很多扩展库可以实现这部分功能。其中最常用的库是multer
,它以Express中间件的形式对form-data
格式提供支持。
Github地址:https://github.com/expressjs/multer
安装multer
库需要执行以下命令:
npm install --save multer
下面例子是一个带有文件上传的表单请求。
const express = require('express');
const multer = require('multer');
const router = express.Router();
const app = express();
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'attachment/')
},
filename: function (req, file, cb) {
const filenameSplited = file.originalname.split('.');
const ext = filenameSplited[filenameSplited.length - 1];
cb(null, file.fieldname + '-' + Date.now() + '.' + ext);
}
})
const upload = multer({ storage });
router.post('/upload', upload.single('file'), (req, res, next) => {
const file = req.file;
res.send('Upload File info [' + JSON.stringify(file) + ']');
});
app.use(router);
app.listen(8080, () => {
console.log('Express server listen on 8080');
});
这段代码稍显复杂,但还是很容易理解的。从上面代码中我们可以看到multer
库的用法:其中storage
对象定义了如何处理文件上传,我们在此指定了文件存储的路径以及如何生成文件名;具体的路由函数中,我们使用了upload.single('file')
作为中间件函数,它表示上传单个文件,表单字段名为file
,该中间件函数内部的逻辑会在我们真正的处理函数前执行。运行上面代码后,我们可以看到请求体中的文件会被自动保存到指定路径,而req.file
中包含的是一些文件的元信息。
再看下面例子代码,它也处理了一个form-data
格式的表单,但它不包含文件上传。
router.post('/student', upload.none(), (req, res, next) => {
const student = req.body;
res.send('Student [' + JSON.stringify(student) + ']');
});
代码中,我们对路由使用了upload.none()
中间件,它表示表单中没有需要上传的文件,我们可以使用req.body
获取解析之后的对象。
其它更多有关multer
库的用法我们可以官方参考文档。
Express中读取请求头直接读取req.headers
这个属性就行了,它是一个包含了请求头键值对的对象。
router.get('/', (req, res, next) => {
const headers = req.headers;
res.send(JSON.stringify(headers));
});
Express中设置响应状态码很简单,我们直接使用res.sendStatus()
函数即可,其参数是HTTP状态码。
router.get('/', (req, res, next) => {
res.sendStatus(404);
});
除此之外,我们也可以使用res.status()
函数,它只是设置响应状态码,但不立即返回,而是需要我们在其后调用end()
、send()
等方法来返回。
如果需要进行302重定向,我们可以使用res.redirect()
函数,其参数是重定向的地址。
router.get('/', (req, res, next) => {
res.redirect('https://www.google.com');
});
Express中可以使用res.json()
函数返回JSON响应,它会自动对数据进行序列化,并设置JSON对应的响应头。
router.get('/', (req, res, next) => {
res.json({
name: "Tom",
age: 18
});
});
注意,如果直接使用res.send()
方法,即使使用了JSON.stringify()
对数据进行序列化,它返回的也只是一个文本字符串,这是因为浏览器只有当识别到响应头Content-Type: application/json; charset=utf-8
时,才会认为响应数据是JSON。
Express中可以使用res.download()
函数,实现对一个特定路径文件的下载服务。
router.get('/', (req, res, next) => {
res.download('attachment/1.docx', '1.docx');
});
上面代码中,我们使用了res.download()
函数,其第一个参数是文件在服务器上的路径,第二个参数是下载的文件名,Express处理该响应时会自动设置Content-Disposition
响应头来指定文件名。
设置响应头我们可以使用res.set()
函数,下面是一个例子。
router.get('/', (req, res, next) => {
res.set('token', 'abc123');
res.sendStatus(200);
});
上面代码中,我们设置了一个token: abc123
的响应头。
Express中我们可以使用res.write()
向响应流写数据,下面是一个例子。
router.get('/', (req, res, next) => {
res.write('some data here...');
res.write('some data here...');
res.write('some data here...');
res.end();
});
上面写法的运行逻辑是3条数据全部写完后,浏览器才认为HTTP请求已经返回。实际上,对于流媒体等数据,我们还可以使用Transfer-Encoding: chunked
实现流式加载,这种方式Express中也并不需要什么特殊设置,只需要手动传递对应的响应头即可,下面是一个例子。
const waitAndWrite = (res) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
res.write('some data here...');
resolve();
}, 1000);
});
};
router.get('/', async (req, res, next) => {
res.set({
'Transfer-Encoding': 'chunked',
'Content-Type': 'text/plain'
});
await waitAndWrite(res);
await waitAndWrite(res);
await waitAndWrite(res);
res.end();
});
Express的处理函数是支持async
异步函数的,因此这里我们封装了一个异步函数waitAndWrite()
,它会等待1秒的时间然后再响流中写一些数据,执行上述代码后我们可以看到浏览器中的数据每隔1秒加载1条的过程。