179 lines
5.6 KiB
JavaScript
179 lines
5.6 KiB
JavaScript
|
|
const http = require('http');
|
|||
|
|
const httpServer = require('http-server');
|
|||
|
|
const { createProxyMiddleware } = require('http-proxy-middleware');
|
|||
|
|
const path = require('path');
|
|||
|
|
const fs = require('fs');
|
|||
|
|
const { PassThrough } = require('stream');
|
|||
|
|
|
|||
|
|
const staticRoot = './html';
|
|||
|
|
const log = (message) => {
|
|||
|
|
const timestamp = new Date().toISOString();
|
|||
|
|
console.log(`[${timestamp}] ${message}`);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const staticServer = httpServer.createServer({
|
|||
|
|
root: staticRoot,
|
|||
|
|
showDir: false,
|
|||
|
|
cors: true,
|
|||
|
|
autoIndex: false
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const proxyConfig = {
|
|||
|
|
path: "/api",
|
|||
|
|
target: "http://172.20.0.2:8090"
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 跟踪响应是否已处理
|
|||
|
|
const responseHandled = new WeakMap();
|
|||
|
|
|
|||
|
|
const apiProxy = createProxyMiddleware({
|
|||
|
|
target: proxyConfig.target,
|
|||
|
|
changeOrigin: true,
|
|||
|
|
pathRewrite: {'^/api': '' },
|
|||
|
|
logLevel: 'silent',
|
|||
|
|
timeout: 120000,
|
|||
|
|
// 禁用内置的分块处理,手动控制
|
|||
|
|
selfHandleResponse: true,
|
|||
|
|
onTimeout: (req, res) => {
|
|||
|
|
if (!responseHandled.has(res)) {
|
|||
|
|
responseHandled.set(res, true);
|
|||
|
|
res.writeHead(504, { 'Content-Type': 'application/json' });
|
|||
|
|
res.end(JSON.stringify({ error: 'Proxy timeout' }));
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
on: {
|
|||
|
|
proxyReq: (proxyReq, req, res) => {
|
|||
|
|
const forwardedUrl = `${proxyReq.protocol}//${proxyReq.host}:${proxyReq.port}${proxyReq.path}`;
|
|||
|
|
log(`转发请求到: ${proxyReq.method} ${forwardedUrl}`);
|
|||
|
|
log(`请求头: ${JSON.stringify(req.headers, null, 2)}`);
|
|||
|
|
|
|||
|
|
const contentType = req.headers['content-type'] || '';
|
|||
|
|
if (contentType.includes('application/json')) {
|
|||
|
|
let requestBody = '';
|
|||
|
|
req.on('data', (chunk) => { requestBody += chunk.toString(); });
|
|||
|
|
req.on('end', () => { log(`请求体: ${requestBody}`); });
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
proxyRes: (proxyRes, req, res) => {
|
|||
|
|
if (responseHandled.has(res)) {
|
|||
|
|
log(`已处理响应,跳过重复处理: ${req.url}`);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
responseHandled.set(res, false);
|
|||
|
|
|
|||
|
|
log(`后端响应: ${proxyRes.statusCode} (${req.url})`);
|
|||
|
|
log(`响应头: ${JSON.stringify(proxyRes.headers, null, 2)}`);
|
|||
|
|
|
|||
|
|
const contentType = proxyRes.headers['content-type'] || '';
|
|||
|
|
const isChunked = proxyRes.headers['transfer-encoding'] === 'chunked';
|
|||
|
|
|
|||
|
|
// 复制响应头(排除可能引起冲突的头)
|
|||
|
|
if (!res.headersSent) {
|
|||
|
|
res.statusCode = proxyRes.statusCode;
|
|||
|
|
Object.keys(proxyRes.headers).forEach(key => {
|
|||
|
|
// 排除分块传输和连接控制头,由Node.js自动处理
|
|||
|
|
if (!['transfer-encoding', 'connection', 'content-length'].includes(key)) {
|
|||
|
|
res.setHeader(key, proxyRes.headers[key]);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理JSON响应
|
|||
|
|
if (contentType.includes('application/json')) {
|
|||
|
|
let responseBody = '';
|
|||
|
|
const logStream = new PassThrough();
|
|||
|
|
|
|||
|
|
// 收集数据用于日志
|
|||
|
|
logStream.on('data', (chunk) => {
|
|||
|
|
responseBody += chunk.toString();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
logStream.on('end', () => {
|
|||
|
|
log(`响应体: ${responseBody}`);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 分块传输处理
|
|||
|
|
if (isChunked) {
|
|||
|
|
// 直接通过流转发,保留分块特性
|
|||
|
|
proxyRes.pipe(logStream).pipe(res);
|
|||
|
|
} else {
|
|||
|
|
// 非分块传输,收集完再发送
|
|||
|
|
proxyRes.on('data', (chunk) => {
|
|||
|
|
responseBody += chunk.toString();
|
|||
|
|
});
|
|||
|
|
proxyRes.on('end', () => {
|
|||
|
|
if (!responseHandled.get(res)) {
|
|||
|
|
responseHandled.set(res, true);
|
|||
|
|
log(`响应体: ${responseBody}`);
|
|||
|
|
res.end(responseBody);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 非JSON响应直接转发
|
|||
|
|
proxyRes.pipe(res);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 监听响应结束
|
|||
|
|
proxyRes.on('end', () => {
|
|||
|
|
responseHandled.set(res, true);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
proxyRes.on('error', (err) => {
|
|||
|
|
if (!responseHandled.get(res)) {
|
|||
|
|
responseHandled.set(res, true);
|
|||
|
|
log(`响应错误: ${err.message}`);
|
|||
|
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|||
|
|
res.end(JSON.stringify({ error: '响应处理错误' }));
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
error: (err, req, res) => {
|
|||
|
|
if (!responseHandled.has(res) || !responseHandled.get(res)) {
|
|||
|
|
responseHandled.set(res, true);
|
|||
|
|
log(`代理错误 [${err.code}]: ${err.message} (请求: ${req.url})`);
|
|||
|
|
if (!res.headersSent) {
|
|||
|
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|||
|
|
}
|
|||
|
|
res.end(JSON.stringify({
|
|||
|
|
error: '代理服务错误',
|
|||
|
|
message: err.message
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const server = http.createServer((req, res) => {
|
|||
|
|
if (req.url.startsWith(proxyConfig.path)) {
|
|||
|
|
log(`收到代理请求: ${req.method} ${req.url}`);
|
|||
|
|
return apiProxy(req, res);
|
|||
|
|
}
|
|||
|
|
else if (req.url === '' || req.url === '/' || req.url.endsWith('/')) {
|
|||
|
|
log(`访问根路径,返回index.html: ${req.url}`);
|
|||
|
|
const indexPath = path.join(staticRoot, 'index.html');
|
|||
|
|
|
|||
|
|
if (fs.existsSync(indexPath)) {
|
|||
|
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|||
|
|
fs.createReadStream(indexPath).pipe(res);
|
|||
|
|
} else {
|
|||
|
|
log(`警告: index.html不存在于 ${staticRoot}`);
|
|||
|
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|||
|
|
res.end('index.html not found');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
log(`处理静态文件请求: ${req.url}`);
|
|||
|
|
staticServer.server.emit('request', req, res);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const port = 8080;
|
|||
|
|
server.listen(port, () => {
|
|||
|
|
log(`静态服务器已启动,监听端口 ${port}`);
|
|||
|
|
log(`静态文件目录: ${path.resolve(staticRoot)}`);
|
|||
|
|
log(`API代理目标: ${proxyConfig.target}`);
|
|||
|
|
log(`访问地址: http://localhost:${port}`);
|
|||
|
|
});
|