likes
comments
collection
share

使用NodeJs做代理服务研究

作者站长头像
站长
· 阅读数 8

利用nodejs做请求的转发

当一个请求发给我们的nodejs服务时,我们想将其转发给其他服务器,并将结果返回给客户端,可以写如下代码:

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

const app = express();
const targetUrl = 'http://your-target-url.com';

// 创建代理路由
app.all('/proxy', async (req, res) => {
  try {
    // 发送请求到目标服务器
    const response = await axios({
      method: req.method,
      url: targetUrl + req.originalUrl,
      headers: req.headers,
      data: req.body
    });

    // 将目标服务器的响应转发到客户端
    res.status(response.status).send(response.data);
  } catch (error) {
    // 处理请求错误
    res.status(500).send('Proxy Error');
  }
});

// 监听代理服务器的端口
const port = 3000;
app.listen(port, function () {
  console.log(`Proxy server is listening on port ${port}`);
});

基于http-proxy写一个基础的代理服务

同样的,我们也可以使用http-proxy来做代理,其仓库有13.5k的star,我们的webpack-dev-server使用的额http-proxy-middleware,也是基于这个库进行的封装,可以说是node写代理服务的标准方案,如下是基础代码。

// 安装 http-proxy 库
npm install http-proxy

const http = require('http');
const httpProxy = require('http-proxy');

// 创建一个代理服务器实例,并配置目标服务器的地址
const proxy = httpProxy.createProxyServer({
  target: 'http://example.com',
});

// 创建一个 HTTP 服务器,并在请求到达时触发代理服务器的转发
const server = http.createServer((req, res) => {
  proxy.web(req, res);
});

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

这样写我们启动服务后,访问http://localhost:3000/

会得到404,寻找原因是因为在代理配置中没有添加changeOrigin:true

const proxy = httpProxy.createProxyServer({
  target: 'http://example.com',
  changeOrigin:true // 添加此配置就可以成功代理
});

// 通过监听proxyReq事件,我们可以打印转发的请求
proxy.on('proxyReq', function(proxyReq, req, res, options) {
  console.log('Proxy Request:', req.method, req.url);
  console.log('Proxy Request Headers:', req.headers); // 从中我们可以看到请求头中的host是http://localhost:3000/,如果不设置changeOrigin:true将导致代理网址拒绝我们的请求
});

如何动态更改代理目标

在进行转发时调用proxy.web方法,传递第三个参数,可以重写定位代理目标

const proxy = httpProxy.createProxyServer({
  target: 'http://example.com', // 默认代理目标
});

// 创建服务器
const server = http.createServer(function (req, res) {
  // 更改代理目标
  const target = 条件 ? 'http://another-example.com' : 'http://other-example.com'
  proxy.web(req, res, { target });
});

如何重写请求header

在使用代理的过程中,我们同样会遇到更改请求头的需求。上面我们监听过proxyReq事件,在这个事件中,你可以更改响应头。

// 通过监听proxyReq事件,我们可以重写请求header,http-proxy会将修改后的请求发给目标服务器
proxy.on('proxyReq', function(proxyReq, req, res, options) {
  proxyReq.setHeader('X-Special-Proxy-Header', 'foobar'); // 添加特殊响应头
  proxyReq.setHeader('Cookie', req.headers.cookie); // 将请求的cookie转发给目标,当然我们不写默认也是这么转发的
  proxyReq.setHeader('Origin', 目标服务器地址); // 这里req.headers.Origin 是我们代理服务器的地址,可能会导致目标服务器不认可我们的请求,导致不能正确响应,可以改成目标服务器地址
  proxyReq.setHeader('Referer', 目标服务器地址); // 同上
});

如何修改响应

除了proxyReq事件,另一个关键事件是proxyRes,通过监听它可以修改返回值。

// 监听proxyRes事件,继续监听其参数proxyRes对象的data和end对象,可以获取到目标服务器返回的响应数据,我们就可以进行更改再返回给客户端
var option = {
  target: target,
  selfHandleResponse : true // 重点,不写这个配置,下面的修改不会生效。
};
proxy.on('proxyRes', function (proxyRes, req, res) {
    var body = [];
    proxyRes.on('data', function (chunk) {
        body.push(chunk);
    });
    proxyRes.on('end', function () {
        body = Buffer.concat(body).toString();
        console.log("res from proxied server:", body);
        res.end("my response to cli");
    });
});
proxy.web(req, res, option);

基于http-proxy-middleware写一个基础的代理服务

在express框架中使用http-proxy-middleware

实际开发中,一般不会直接使用http-proxy,而是会使用http-proxy-middleware,以中间件的方式融入我们的框架,这种方式更加契合。http-proxy-middleware内部仍然使用的http-proxy,只是做了一层封装。

注意:默认会安装http-proxy-middleware 2.x.x版本,但是官网上的文档是针对3.0.0-beta.1的,2和3版本的api差异较大,为了使用更先进的功能,还是要安装3.0.0-beta.1版本。

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

const app = express();

// 创建代理中间件
const proxyMiddleware = createProxyMiddleware({
  target: 'http://example.com', // 设置代理目标地址
  changeOrigin: true, // 修改请求头中的 Host 字段为目标地址
  // 其他可选配置项...
});

// 将代理中间件应用到所有路由
app.use('/', proxyMiddleware);

// 启动 Express 服务器
app.listen(3000, () => {
  console.log('Proxy server listening on port 3000');
});

在Koa框架中使用http-proxy-middleware

const Koa = require('koa')
const { createProxyMiddleware } = require('http-proxy-middleware')
const koaConnect = require('koa2-connect')

const app = new Koa()

// 创建代理中间件
const proxyMiddleware = createProxyMiddleware({
  target: 'http://example.com', // 替换为目标网址
  changeOrigin: true // 设置为true以更改请求的原始主机头
})

// 使用koa2-connect将代理中间件转换为Koa中间件
const proxyHandler = async (ctx, next) => {
  // console.log('打印')
  await koaConnect(proxyMiddleware)(ctx, next)
}

// 应用代理中间件
app.use(proxyHandler)

app.listen(3000, () => {
  console.log('Server is running on port 3000')
})

这里值得注意的点是http-proxy-middleware中间件不能直接给Koa使用,因为Express和Koa中间件的入参不相同,需要使用koa2-connect帮忙转换一下。

使用express还是Koa关系不大,想写好代理服务,重点还是对于http-proxy-middleware的了解和使用。

http-proxy和http-proxy-middleware有什么区别

http-proxy-middleware是基于http-proxy为Express封装的中间件,简化了代理配置的过程,并提供了一些方便的方法和选项来处理常见的代理需求。

const proxyMiddleware = createProxyMiddleware({
    // 比如重写请求路径的规则。可以是一个简单的对象,将匹配的路径部分替换为指定的值。看这个配置有没有一点熟悉呢,我们常用的webpack-dev-server也会使用这个配置。
    pathRewrite: {
      '^/api': '' // 将所有以 '/api' 开头的路径替换为空字符串
    } 
    //  使用pathFilter可以轻松过滤哪些请求需要使用代理服务,哪些路由不使用
    pathFilter: function(path, req) {
        return path.startsWith('/remote/debug/');  
    },
    // pathFilter也可以配置成一个数组
    // pathFilter: ['/api/**', '/ajax/**']
})

如何动态更改代理目标

const proxyMiddleware = createProxyMiddleware({
    // 通过router方法可以重定向代理目标,这个配置也只在http-proxy-middleware中有,http-proxy实现同样功能要通过proxy.web方法的第三个配置参数实现。
    router: function(req) { 
        const target = 条件 ? 'http://another-example.com' : 'http://other-example.com'
        return target;
    }
    
    // router也可以配置成一个对象
    router: {
        '/api1': 'http://server1.com',
        '/api2': 'http://server2.com',
        '/api3': 'http://server3.com',
        'staging.localhost:3000' : 'http://127.0.0.1:8002',  // host only
        'localhost:3000/api' : 'http://127.0.0.1:8003',  // host + path
     },
})

如何重写请求header

在http-proxy-middleware 3.0.0-beta中,将http-proxy原有的事件放在了on对象下:

onst proxyMiddleware = createProxyMiddleware({
  target: 'http://example.com', // 替换为目标网址
  changeOrigin: true, // 设置为true。heander中host更改的原始主机头 
  on: {
    // 原http-proxy的proxyReq事件
     proxyReq: (proxyReq, req, res) => { 
    },
    // 原http-proxy的proxyRes事件
    proxyRes: (proxyRes, req, res) => { 
    },
  }
})

所以我们想更改请求头,在proxyReq方法中写跟http-proxy一样的代码即可

on: { 
    proxyReq: (proxyReq, req, res) => { 
        proxyReq.setHeader('Origin', 目标服务器地址); // 这里req.headers.Origin 是我们代理服务器的地址,可能会导致目标服务器不认可我们的请求,导致不能正确响应,可以改成目标服务器地址
        proxyReq.setHeader('Referer', 目标服务器地址); // 同上
    }, 
}

如何修改响应

http-proxy-middleware修改响应跟在http-proxy的写法不同,使用http-proxy的写法是不生效的。要使用responseInterceptor方法进行响应数据拦截和修改:

on: {
    proxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
      const response = responseBuffer.toString('utf8'); // convert buffer to string
      return response.replace('Hello', 'Goodbye'); // manipulate response and return the result
    }),
 },

如果你只希望修改某些特定请求的响应值,其他请求仍然使用目标服务给的响应,可以这么写:

selfHandleResponse : true,  // 一定要写,不写修改的响应不会生效
on: {
    proxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
      // 如果请求的是html页面,则进行更改, 否则返回原响应
      if (proxyRes.headers['content-type'] && proxyRes.headers['content-type'].includes('text/html')) {
         const response = responseBuffer.toString('utf8'); // convert buffer to string
          return response.replace('Hello', 'Goodbye'); // manipulate response and return
      }
      return responseBuffer;
    }),
 },

总结

这篇文章初步认识了一下如何使用Nodejs做代理服务。如果相对Nodejs做代理有更多的了解,可以查看http-proxyhttp-proxy-middlewire仓库上的更多内容。