serve.cjs 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. const http = require('http')
  2. const fs = require('fs')
  3. const path = require('path')
  4. const PORT = 3001
  5. const BACKEND = 'http://127.0.0.1:8583'
  6. const STATIC_DIR = path.join(__dirname, 'dist', 'build', 'h5')
  7. const API_PREFIX = '/forge-h5-api'
  8. const APP_BASE = '/forge-h5'
  9. const MIME_TYPES = {
  10. '.html': 'text/html; charset=utf-8',
  11. '.js': 'application/javascript; charset=utf-8',
  12. '.css': 'text/css; charset=utf-8',
  13. '.json': 'application/json; charset=utf-8',
  14. '.png': 'image/png',
  15. '.jpg': 'image/jpeg',
  16. '.jpeg': 'image/jpeg',
  17. '.gif': 'image/gif',
  18. '.svg': 'image/svg+xml',
  19. '.ico': 'image/x-icon',
  20. '.woff': 'font/woff',
  21. '.woff2': 'font/woff2',
  22. '.ttf': 'font/ttf',
  23. '.map': 'application/json',
  24. }
  25. function getMimeType(filePath) {
  26. const ext = path.extname(filePath).toLowerCase()
  27. return MIME_TYPES[ext] || 'application/octet-stream'
  28. }
  29. function proxyRequest(req, res) {
  30. const url = req.url.replace(API_PREFIX, '')
  31. const options = {
  32. hostname: '127.0.0.1',
  33. port: 8583,
  34. path: url,
  35. method: req.method,
  36. headers: {
  37. ...req.headers,
  38. host: '127.0.0.1:8583',
  39. },
  40. }
  41. const proxyReq = http.request(options, (proxyRes) => {
  42. // 复制响应头
  43. const headers = {}
  44. for (const [key, value] of Object.entries(proxyRes.headers)) {
  45. if (key !== 'transfer-encoding') {
  46. headers[key] = value
  47. }
  48. }
  49. res.writeHead(proxyRes.statusCode, headers)
  50. proxyRes.pipe(res)
  51. })
  52. proxyReq.on('error', (err) => {
  53. console.error(`[API Proxy Error] ${req.method} ${url}:`, err.message)
  54. res.writeHead(502, { 'Content-Type': 'application/json' })
  55. res.end(JSON.stringify({ error: 'Backend service unavailable' }))
  56. })
  57. req.pipe(proxyReq)
  58. }
  59. function serveStatic(req, res) {
  60. let urlPath = req.url
  61. // 去掉 /forge-h5 前缀,映射到静态文件目录
  62. if (urlPath.startsWith(APP_BASE)) {
  63. urlPath = urlPath.slice(APP_BASE.length) || '/'
  64. }
  65. let filePath = path.join(STATIC_DIR, urlPath === '/' ? '/index.html' : urlPath)
  66. // 安全检查:防止路径遍历
  67. if (!filePath.startsWith(STATIC_DIR)) {
  68. res.writeHead(403)
  69. res.end('Forbidden')
  70. return
  71. }
  72. fs.readFile(filePath, (err, data) => {
  73. if (err) {
  74. // SPA 回退:如果文件不存在,返回 index.html
  75. fs.readFile(path.join(STATIC_DIR, 'index.html'), (err2, data2) => {
  76. if (err2) {
  77. res.writeHead(500)
  78. res.end('Internal Server Error')
  79. return
  80. }
  81. res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
  82. res.end(data2)
  83. })
  84. return
  85. }
  86. res.writeHead(200, { 'Content-Type': getMimeType(filePath) })
  87. res.end(data)
  88. })
  89. }
  90. const server = http.createServer((req, res) => {
  91. // API 请求代理到后端
  92. if (req.url.startsWith(API_PREFIX)) {
  93. console.log(`[API] ${req.method} ${req.url} -> ${BACKEND}${req.url.replace(API_PREFIX, '')}`)
  94. proxyRequest(req, res)
  95. } else {
  96. // 静态文件服务
  97. console.log(`[Static] ${req.method} ${req.url}`)
  98. serveStatic(req, res)
  99. }
  100. })
  101. server.listen(PORT, '0.0.0.0', () => {
  102. console.log(`Server running at http://0.0.0.0:${PORT}`)
  103. console.log(`Static files: ${STATIC_DIR}`)
  104. console.log(`API proxy: ${API_PREFIX} -> ${BACKEND}`)
  105. })