处理HTTP请求

这篇笔记我们介绍Express如何处理各种类型的HTTP请求,以及如何输出HTTP响应。

编写处理函数

前面有关路由的代码中,其实我们一直在使用处理函数。Express框架的处理函数支持3个参数:reqresnext,前两个参数分别封装了请求上下文和响应对象。

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');
});

读取请求参数

读取URL参数

我们可以使用req.query读取URL参数。

router.get('/student', (req, res, next) => {
  const id = req.query.id;
  res.send('Student ID [' + id + ']');
});

上面例子代码中,我们读取了id这个URL参数。

读取Restful路径参数

对于Restful路径参数,前面介绍过可以使用类似:id的形式定义路由,取参数时我们可以使用req.params

router.get('/student/:id', (req, res, next) => {
  const id = req.params.id;
  res.send('Student ID [' + id + ']');
});

读取JSON请求体

读取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-urlencodedform-data两种格式,Express内置了对于前者的支持,而后者需要安装扩展来支持。

x-www-form-urlencoded表单

对于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表单和文件上传

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));
});

返回响应

设置HTTP状态码

Express中设置响应状态码很简单,我们直接使用res.sendStatus()函数即可,其参数是HTTP状态码。

router.get('/', (req, res, next) => {
  res.sendStatus(404);
});

除此之外,我们也可以使用res.status()函数,它只是设置响应状态码,但不立即返回,而是需要我们在其后调用end()send()等方法来返回。

返回HTTP重定向

如果需要进行302重定向,我们可以使用res.redirect()函数,其参数是重定向的地址。

router.get('/', (req, res, next) => {
  res.redirect('https://www.google.com');
});

返回JSON数据

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条的过程。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap