分析和解决 http-proxy 代理中文乱码问题

739 阅读3分钟

问题背景

我们使用 http-proxy开发了一个代理服务用于测试调试,有同学发现走代理服务时部分中文字符出现了乱码。这个问题并非每次都发生,但在特定条件下(例如,处理大文本数据或某些特定字符时)容易复现。

排查过程

第一步:确定问题范围

首先,我们排除客户端编码问题,直接通过cURL请求查看结果,确认问题是在处理代理请求时出现,而非客户端编码问题。具体表现在代理服务器返回的响应中包含乱码的中文字符。我们的初步怀疑是字符编码的问题。

第二步:检查 Content-Type

为了确保浏览器或客户端正确解码,我们检查了响应头中的 Content-Type 设置。为了防止乱码,我们通常会在响应头中添加 charset=utf-8

this.httpProxy.on('proxyRes', (proxyRes, req, res) => {
    const contentType = proxyRes.headers['content-type'];
    if (contentType && !contentType.includes('charset')) {
      proxyRes.headers['content-type'] = `${contentType}; charset=utf-8`;
    }
})


虽然我们添加了这个设置,但问题依旧存在。因此,我们排除了响应头的直接问题。

第三步:分析数据流处理

由于我们的 mock 服务支持修改 response ,所以在获取流和返回流之间还有2个步骤需要检查

1.解析流

2.修改流

接下来,我们先检查数据流处理代码。原始代码如下:

function parserStringBody (req: IncomingMessage | Gunzip | Inflate | BrotliDecompress) {
  return new Promise((resolve, reject) => {
    let body = '';
    req.on('data', (chunk) => {
      body += chunk;
    });
    req.on('end', () => {
      try {
        resolve(body);
      } catch (error) {
        reject(new Error('Failed to parse string data'));
      }
    });
    req.on('error', (error) => {
      reject(error);
    });
  })
}

第四步:识别问题根源

经过多次测试,我们发现问题的根源在于直接使用 += 操作符拼接 Buffer 对象。Buffer 对象表示二进制数据,直接拼接会导致字符编码问题,尤其是对多字节字符(如中文)而言。

第五步:寻找解决方案

为了正确处理字符编码,我们需要使用 Buffer 对象存储所有的 chunk 数据,并在 end 事件中一次性将其转换为字符串。

最终解决方法 我们改进了 parserStringBody 函数,确保正确处理数据流,并进行必要的编码转换:

import { IncomingMessage } from 'http';
import { Gunzip, Inflate, BrotliDecompress } from 'zlib';

export function parserStringBody(req: IncomingMessage | Gunzip | Inflate | BrotliDecompress): Promise<string> {
  return new Promise<string>((resolve, reject) => {
    const chunks: Buffer[] = [];
    req.on('data', (chunk) => {
      chunks.push(chunk);
    });
    req.on('end', () => {
      try {
        const buffer = Buffer.concat(chunks);
        resolve(buffer.toString('utf-8'));
      } catch (error) {
        reject(new Error('Failed to parse string data'));
      }
    });
    req.on('error', (error) => {
      reject(error);
    });
  });
}

完整代码:

import { Request, Response } from 'express';
import httpProxy from 'http-proxy';
import { parserStringBody } from './parserStringBody';

const proxy = httpProxy.createProxyServer({ selfHandleResponse: true });

proxy.on('proxyRes', async (proxyRes, req, res) => {
  try {
    const body = await parserStringBody(decompressStream(proxyRes, proxyRes.headers['content-encoding']);
    res.setHeader('Content-Type', 'application/json; charset=utf-8');
    res.end(body);
  } catch (error) {
    res.statusCode = 500;
    res.end('Internal Server Error');
  }
});

app.use('/proxy', (req, res) => {
  proxy.web(req, res, { target: 'http://example.com' });
});

结论

通过正确处理 Buffer 对象并确保数据流在合适的时机转换为字符串,我们成功解决了代理过程中出现的中文乱码问题。这个过程让我们深入理解了 Node.js 数据流处理和字符编码的重要性。在未来的开发中,我们会更加注意数据流的正确处理,避免类似问题的发生。