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}`); });