问题背景
我们使用 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 数据流处理和字符编码的重要性。在未来的开发中,我们会更加注意数据流的正确处理,避免类似问题的发生。