MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Node.js HTTP 请求的拦截与修改

2022-10-204.2k 阅读

1. 理解 HTTP 请求拦截与修改的概念

在前端开发中,Node.js 作为一个强大的后端运行环境,为我们提供了处理 HTTP 请求的丰富能力。HTTP 请求拦截与修改指的是在请求发出或者接收到请求的过程中,对请求的内容进行截取并按照我们的需求进行更改的操作。

这种操作在很多场景下都非常有用。比如在开发代理服务器时,我们可能需要对客户端发送的请求进行修改,添加一些自定义的请求头,以满足后端服务器的特定要求。又或者在进行安全防护时,拦截并修改请求中的敏感信息,防止信息泄露。

2. 利用 Node.js 原生模块进行请求拦截与修改

Node.js 的 http 模块是处理 HTTP 相关操作的核心模块。我们可以通过创建一个 HTTP 服务器来拦截和修改请求。

2.1 创建基本的 HTTP 服务器

首先,我们来创建一个简单的 HTTP 服务器,代码如下:

const http = require('http');

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello, World!');
});

const port = 3000;
server.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

在上述代码中,我们使用 http.createServer 方法创建了一个 HTTP 服务器。这个服务器会在接收到请求时,向客户端返回一个简单的 “Hello, World!” 响应。

2.2 拦截请求并修改请求头

接下来,我们尝试在请求到达服务器时,拦截请求并修改请求头。假设我们要为所有的请求添加一个自定义的请求头 X-Custom-Header

const http = require('http');

const server = http.createServer((req, res) => {
  // 拦截请求并修改请求头
  req.headers['x-custom-header'] = 'This is a custom header';

  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Request intercepted and header modified');
});

const port = 3000;
server.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

在这个例子中,我们直接在 req.headers 对象上添加了一个新的属性 x-custom-header,从而实现了对请求头的修改。

2.3 拦截并修改请求体

如果请求包含请求体(比如 POST 请求),我们可以通过监听 data 事件来获取请求体数据,并进行修改。

const http = require('http');
const querystring = require('querystring');

const server = http.createServer((req, res) => {
  let body = '';
  req.on('data', (chunk) => {
    body += chunk.toString();
  });

  req.on('end', () => {
    if (req.method === 'POST' && req.headers['content-type'] === 'application/x-www-form-urlencoded') {
      const data = querystring.parse(body);
      // 修改请求体数据
      data.newField = 'This is a new field added on the server';
      const newBody = querystring.stringify(data);

      // 响应修改后的内容
      res.writeHead(200, { 'Content-Type': 'text/plain' });
      res.end('Request body modified:'+ newBody);
    } else {
      res.writeHead(400, { 'Content-Type': 'text/plain' });
      res.end('Unsupported request type');
    }
  });
});

const port = 3000;
server.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

在这段代码中,我们首先通过 req.on('data',...) 事件监听来收集请求体数据。当 end 事件触发时,表示请求体数据已经接收完毕。然后我们判断请求方法和内容类型,如果是 POST 且内容类型为 application/x-www-form-urlencoded,我们使用 querystring.parse 方法解析请求体数据,修改数据后再使用 querystring.stringify 方法将其转换回字符串格式。

3. 使用 Express 框架进行请求拦截与修改

Express 是 Node.js 中最流行的 Web 应用框架之一,它提供了更简洁和强大的方式来处理 HTTP 请求。

3.1 安装 Express

首先,我们需要通过 npm 安装 Express:

npm install express

3.2 创建基本的 Express 应用

以下是一个简单的 Express 应用示例:

const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
  res.send('Hello, World!');
});

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

3.3 拦截请求并修改请求头

在 Express 中,我们可以使用中间件来拦截和修改请求头。中间件是一个函数,它可以访问请求对象 (req)、响应对象 (res) 以及应用的请求 - 响应循环中的下一个中间件。

const express = require('express');
const app = express();
const port = 3000;

// 定义中间件来修改请求头
app.use((req, res, next) => {
  req.headers['x-custom-header'] = 'Express custom header';
  next();
});

app.get('/', (req, res) => {
  res.send('Request intercepted and header modified by Express');
});

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

在这个例子中,我们使用 app.use 方法定义了一个中间件。这个中间件在所有路由之前执行,为请求添加了一个自定义的请求头 x-custom-header,然后通过调用 next() 方法将控制权传递给下一个中间件或路由处理函数。

3.4 拦截并修改请求体

Express 提供了内置的中间件来处理不同类型的请求体,比如 express.json()express.urlencoded()。我们可以结合这些中间件来拦截并修改请求体。

const express = require('express');
const app = express();
const port = 3000;

app.use(express.urlencoded({ extended: true }));

app.post('/', (req, res) => {
  // 修改请求体数据
  req.body.newField = 'Added by Express';

  res.send('Request body modified by Express:'+ JSON.stringify(req.body));
});

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

在这个例子中,我们使用 express.urlencoded({ extended: true }) 中间件来解析 application/x-www-form-urlencoded 格式的请求体。在路由处理函数中,我们直接修改 req.body 对象,添加了一个新的字段 newField

4. 使用代理服务器进行请求拦截与修改

代理服务器在前端开发中扮演着重要的角色,特别是在开发过程中进行调试或者在生产环境中进行负载均衡等场景。我们可以利用 Node.js 搭建一个代理服务器,并在代理过程中拦截和修改请求。

4.1 安装 http - proxy - middleware

http - proxy - middleware 是一个用于创建 HTTP 代理服务器的中间件。我们可以通过 npm 安装它:

npm install http-proxy-middleware

4.2 创建代理服务器并拦截请求

以下是一个使用 http - proxy - middleware 创建代理服务器并拦截请求修改请求头的示例:

const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');

const app = express();
const target = 'http://example.com';

const proxy = createProxyMiddleware({
  target: target,
  changeOrigin: true,
  onProxyReq: (proxyReq, req, res) => {
    // 修改请求头
    proxyReq.setHeader('x - custom - header', 'Proxy custom header');
  }
});

app.use(proxy);

const port = 3000;
app.listen(port, () => {
  console.log(`Proxy server running on port ${port}`);
});

在这个例子中,我们使用 createProxyMiddleware 创建了一个代理服务器,目标服务器是 http://example.com。通过 onProxyReq 回调函数,我们在代理请求发送之前,修改了请求头,添加了一个自定义的请求头 x - custom - header

4.3 拦截并修改代理请求体

要拦截并修改代理请求体,稍微复杂一些,因为我们需要处理请求体的数据流。以下是一个示例:

const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const http = require('http');
const { Readable } = require('stream');

const app = express();
const target = 'http://example.com';

const proxy = createProxyMiddleware({
  target: target,
  changeOrigin: true,
  onProxyReq: (proxyReq, req, res) => {
    if (req.method === 'POST' && req.headers['content - type'] === 'application/json') {
      let body = '';
      req.on('data', (chunk) => {
        body += chunk.toString();
      });

      req.on('end', () => {
        try {
          const data = JSON.parse(body);
          // 修改请求体数据
          data.newField = 'Added in proxy';
          const newBody = JSON.stringify(data);

          proxyReq.setHeader('Content - Length', Buffer.byteLength(newBody));
          const newStream = Readable.from(newBody);
          newStream.pipe(proxyReq);
        } catch (error) {
          console.error('Error parsing request body:', error);
          res.status(400).send('Bad Request');
        }
      });
    }
  }
});

app.use(proxy);

const port = 3000;
app.listen(port, () => {
  console.log(`Proxy server running on port ${port}`);
});

在这个代码中,我们首先判断请求是否为 POST 且内容类型为 application/json。如果是,我们通过监听 dataend 事件来获取请求体数据,解析并修改数据后,创建一个新的可读流,并将其管道到 proxyReq 中,同时设置正确的 Content - Length 头。

5. 深入理解请求拦截与修改的底层原理

无论是使用 Node.js 原生模块、Express 框架还是代理服务器进行请求拦截与修改,其底层原理都涉及到对 HTTP 协议的理解。

HTTP 是一个基于请求 - 响应模型的应用层协议。当客户端发送一个 HTTP 请求时,请求包含请求行、请求头和请求体(对于一些请求方法,如 GET,可能没有请求体)。服务器接收到请求后,根据请求的内容进行处理,并返回一个响应,响应同样包含状态行、响应头和响应体。

在 Node.js 中,http 模块提供了创建 HTTP 服务器和客户端的能力。当我们创建一个 HTTP 服务器时,createServer 方法的回调函数接收 req(请求对象)和 res(响应对象)作为参数。req 对象包含了关于请求的所有信息,包括请求头、请求方法、请求 URL 等。我们可以直接操作 req 对象来修改请求的相关信息。

对于请求体,由于它可能是一个数据流(特别是在大数据量的情况下),我们需要通过监听 data 事件来逐步接收数据,当 end 事件触发时,表示数据接收完毕,此时我们可以对完整的数据进行处理和修改。

在 Express 框架中,它基于 http 模块进行了封装,提供了更简洁的路由和中间件机制。中间件本质上就是一个函数,它可以在请求到达路由处理函数之前或者之后执行一些操作,比如修改请求头、解析请求体等。

代理服务器的原理则是在客户端和目标服务器之间充当一个中间人。客户端的请求先到达代理服务器,代理服务器根据配置将请求转发到目标服务器,并将目标服务器的响应返回给客户端。在转发请求的过程中,我们可以通过 http - proxy - middleware 等工具提供的回调函数来拦截和修改请求。

6. 注意事项与常见问题

6.1 数据完整性

在拦截和修改请求体时,要特别注意数据的完整性。确保在修改数据后,正确设置 Content - Length 头,尤其是对于二进制数据或者较大的请求体。如果 Content - Length 头设置不正确,可能会导致目标服务器无法正确解析请求。

6.2 兼容性

不同的后端服务器对请求头和请求体的格式要求可能不同。在修改请求时,要确保修改后的请求符合目标服务器的要求,否则可能会导致请求失败。例如,一些服务器对请求头的大小写敏感,要注意按照服务器的规范进行修改。

6.3 性能问题

频繁地拦截和修改请求可能会对服务器的性能产生一定的影响,特别是在高并发的场景下。尽量优化代码,避免不必要的计算和数据处理,以减少对性能的影响。例如,在处理请求体数据时,避免多次解析和序列化数据。

6.4 安全问题

在拦截和修改请求时,要注意安全问题。不要随意暴露敏感信息,比如在修改请求头时,不要添加可能泄露服务器内部信息的头字段。同时,对于来自客户端的请求数据,要进行充分的验证和过滤,防止恶意攻击,如 SQL 注入、XSS 攻击等。

7. 实践案例

假设我们正在开发一个电商平台,前端应用通过 Node.js 服务器与后端 API 进行通信。在某些情况下,我们需要对前端发送到后端的请求进行一些修改。

7.1 添加用户认证信息

假设后端 API 需要在请求头中携带用户认证令牌 Authorization。我们可以在 Node.js 服务器(使用 Express 框架)中通过中间件为所有请求添加这个认证信息。

const express = require('express');
const app = express();

// 假设这里从某个地方获取到用户认证令牌
const authToken = 'valid - auth - token';

// 中间件添加认证头
app.use((req, res, next) => {
  req.headers['Authorization'] = `Bearer ${authToken}`;
  next();
});

// 代理到后端 API
const { createProxyMiddleware } = require('http - proxy - middleware');
const apiProxy = createProxyMiddleware({
  target: 'http://backend - api.com',
  changeOrigin: true
});
app.use('/api', apiProxy);

const port = 3000;
app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

在这个例子中,我们通过中间件为所有请求添加了 Authorization 头,这样前端应用发出的请求在到达后端 API 时就携带了正确的认证信息。

7.2 修改请求参数以适应后端 API 变化

假设后端 API 对某个接口的参数格式进行了调整,之前是使用 user_id 作为参数名,现在改为 userID。我们可以在 Node.js 服务器中拦截请求并修改参数名。

const express = require('express');
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

app.post('/api/user', (req, res) => {
  if (req.body.user_id) {
    req.body.userID = req.body.user_id;
    delete req.body.user_id;
  }
  // 这里可以继续处理请求并转发到后端 API
  res.send('Request parameters modified');
});

const port = 3000;
app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

在这个例子中,我们在处理 /api/user 这个 POST 请求时,检查请求体中是否存在 user_id 参数,如果存在则将其转换为 userID,并删除原来的 user_id 参数,从而使请求能够适应后端 API 的变化。

通过以上详细的讲解、代码示例以及实践案例,我们对 Node.js 中 HTTP 请求的拦截与修改有了较为深入的理解和掌握。无论是在开发代理服务器、Web 应用还是进行各种 HTTP 相关的处理时,这些技术都能为我们提供强大的功能支持,帮助我们更好地满足业务需求。同时,在实际应用中要注意遵循相关的规范和原则,确保程序的稳定性、性能和安全性。