rwz-dev-proxy 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/bin/rwz-dev-proxy.js +29 -0
  2. package/db.js +121 -0
  3. package/index.js +625 -0
  4. package/package.json +32 -0
  5. package/public/cdn/axios/dist/axios.min.js +3 -0
  6. package/public/cdn/element-plus/dist/index.css +1 -0
  7. package/public/cdn/element-plus/dist/index.full.js +72239 -0
  8. package/public/cdn/mockjs/dist/mock-min.js +10 -0
  9. package/public/cdn/monaco-editor/min/vs/_commonjsHelpers-CT9FvmAN.js +1 -0
  10. package/public/cdn/monaco-editor/min/vs/abap-D-t0cyap.js +1 -0
  11. package/public/cdn/monaco-editor/min/vs/apex-CcIm7xu6.js +1 -0
  12. package/public/cdn/monaco-editor/min/vs/assets/css.worker-HnVq6Ewq.js +93 -0
  13. package/public/cdn/monaco-editor/min/vs/assets/editor.worker-Be8ye1pW.js +26 -0
  14. package/public/cdn/monaco-editor/min/vs/assets/html.worker-B51mlPHg.js +470 -0
  15. package/public/cdn/monaco-editor/min/vs/assets/json.worker-DKiEKt88.js +58 -0
  16. package/public/cdn/monaco-editor/min/vs/assets/ts.worker-CMbG-7ft.js +67731 -0
  17. package/public/cdn/monaco-editor/min/vs/azcli-BA0tQDCg.js +1 -0
  18. package/public/cdn/monaco-editor/min/vs/basic-languages/monaco.contribution.js +1 -0
  19. package/public/cdn/monaco-editor/min/vs/bat-C397hTD6.js +1 -0
  20. package/public/cdn/monaco-editor/min/vs/bicep-DF5aW17k.js +2 -0
  21. package/public/cdn/monaco-editor/min/vs/cameligo-plsz8qhj.js +1 -0
  22. package/public/cdn/monaco-editor/min/vs/clojure-Y2auQMzK.js +1 -0
  23. package/public/cdn/monaco-editor/min/vs/coffee-Bu45yuWE.js +1 -0
  24. package/public/cdn/monaco-editor/min/vs/cpp-CkKPQIni.js +1 -0
  25. package/public/cdn/monaco-editor/min/vs/csharp-CX28MZyh.js +1 -0
  26. package/public/cdn/monaco-editor/min/vs/csp-D8uWnyxW.js +1 -0
  27. package/public/cdn/monaco-editor/min/vs/css-CaeNmE3S.js +3 -0
  28. package/public/cdn/monaco-editor/min/vs/cssMode-CjiAH6dQ.js +1 -0
  29. package/public/cdn/monaco-editor/min/vs/cypher-DVThT8BS.js +1 -0
  30. package/public/cdn/monaco-editor/min/vs/dart-CmGfCvrO.js +1 -0
  31. package/public/cdn/monaco-editor/min/vs/dockerfile-CZqqYdch.js +1 -0
  32. package/public/cdn/monaco-editor/min/vs/ecl-30fUercY.js +1 -0
  33. package/public/cdn/monaco-editor/min/vs/editor/editor.main.css +1 -0
  34. package/public/cdn/monaco-editor/min/vs/editor/editor.main.js +5 -0
  35. package/public/cdn/monaco-editor/min/vs/editor.api-CalNCsUg.js +903 -0
  36. package/public/cdn/monaco-editor/min/vs/elixir-xjPaIfzF.js +1 -0
  37. package/public/cdn/monaco-editor/min/vs/flow9-DqtmStfK.js +1 -0
  38. package/public/cdn/monaco-editor/min/vs/freemarker2-Cz_sV6Md.js +3 -0
  39. package/public/cdn/monaco-editor/min/vs/fsharp-BOMdg4U1.js +1 -0
  40. package/public/cdn/monaco-editor/min/vs/go-D_hbi-Jt.js +1 -0
  41. package/public/cdn/monaco-editor/min/vs/graphql-CKUU4kLG.js +1 -0
  42. package/public/cdn/monaco-editor/min/vs/handlebars-OwglfO-1.js +1 -0
  43. package/public/cdn/monaco-editor/min/vs/hcl-DTaboeZW.js +1 -0
  44. package/public/cdn/monaco-editor/min/vs/html-Pa1xEWsY.js +1 -0
  45. package/public/cdn/monaco-editor/min/vs/htmlMode-Bz67EXwp.js +1 -0
  46. package/public/cdn/monaco-editor/min/vs/ini-CsNwO04R.js +1 -0
  47. package/public/cdn/monaco-editor/min/vs/java-CI4ZMsH9.js +1 -0
  48. package/public/cdn/monaco-editor/min/vs/javascript-PczUCGdz.js +1 -0
  49. package/public/cdn/monaco-editor/min/vs/jsonMode-DULH5oaX.js +7 -0
  50. package/public/cdn/monaco-editor/min/vs/julia-BwzEvaQw.js +1 -0
  51. package/public/cdn/monaco-editor/min/vs/kotlin-IUYPiTV8.js +1 -0
  52. package/public/cdn/monaco-editor/min/vs/language/css/monaco.contribution.js +1 -0
  53. package/public/cdn/monaco-editor/min/vs/language/html/monaco.contribution.js +1 -0
  54. package/public/cdn/monaco-editor/min/vs/language/json/monaco.contribution.js +1 -0
  55. package/public/cdn/monaco-editor/min/vs/language/typescript/monaco.contribution.js +1 -0
  56. package/public/cdn/monaco-editor/min/vs/less-C0eDYdqa.js +2 -0
  57. package/public/cdn/monaco-editor/min/vs/lexon-iON-Kj97.js +1 -0
  58. package/public/cdn/monaco-editor/min/vs/liquid-DqKjdPGy.js +1 -0
  59. package/public/cdn/monaco-editor/min/vs/loader.js +1368 -0
  60. package/public/cdn/monaco-editor/min/vs/lspLanguageFeatures-kM9O9rjY.js +4 -0
  61. package/public/cdn/monaco-editor/min/vs/lua-DtygF91M.js +1 -0
  62. package/public/cdn/monaco-editor/min/vs/m3-CsR4AuFi.js +1 -0
  63. package/public/cdn/monaco-editor/min/vs/markdown-C_rD0bIw.js +1 -0
  64. package/public/cdn/monaco-editor/min/vs/mdx-DEWtB1K5.js +1 -0
  65. package/public/cdn/monaco-editor/min/vs/mips-CiYP61RB.js +1 -0
  66. package/public/cdn/monaco-editor/min/vs/monaco.contribution-D2OdxNBt.js +1 -0
  67. package/public/cdn/monaco-editor/min/vs/monaco.contribution-DO3azKX8.js +1 -0
  68. package/public/cdn/monaco-editor/min/vs/monaco.contribution-EcChJV6a.js +1 -0
  69. package/public/cdn/monaco-editor/min/vs/monaco.contribution-qLAYrEOP.js +1 -0
  70. package/public/cdn/monaco-editor/min/vs/msdax-C38-sJlp.js +1 -0
  71. package/public/cdn/monaco-editor/min/vs/mysql-CdtbpvbG.js +1 -0
  72. package/public/cdn/monaco-editor/min/vs/nls.messages-loader.js +1 -0
  73. package/public/cdn/monaco-editor/min/vs/nls.messages.cs.js.js +17 -0
  74. package/public/cdn/monaco-editor/min/vs/nls.messages.de.js.js +17 -0
  75. package/public/cdn/monaco-editor/min/vs/nls.messages.es.js.js +17 -0
  76. package/public/cdn/monaco-editor/min/vs/nls.messages.fr.js.js +15 -0
  77. package/public/cdn/monaco-editor/min/vs/nls.messages.it.js.js +15 -0
  78. package/public/cdn/monaco-editor/min/vs/nls.messages.ja.js.js +17 -0
  79. package/public/cdn/monaco-editor/min/vs/nls.messages.js.js +10 -0
  80. package/public/cdn/monaco-editor/min/vs/nls.messages.ko.js.js +25 -0
  81. package/public/cdn/monaco-editor/min/vs/nls.messages.pl.js.js +17 -0
  82. package/public/cdn/monaco-editor/min/vs/nls.messages.pt-br.js.js +6 -0
  83. package/public/cdn/monaco-editor/min/vs/nls.messages.ru.js.js +17 -0
  84. package/public/cdn/monaco-editor/min/vs/nls.messages.tr.js.js +15 -0
  85. package/public/cdn/monaco-editor/min/vs/nls.messages.zh-cn.js.js +17 -0
  86. package/public/cdn/monaco-editor/min/vs/nls.messages.zh-tw.js.js +15 -0
  87. package/public/cdn/monaco-editor/min/vs/objective-c-CntZFaHX.js +1 -0
  88. package/public/cdn/monaco-editor/min/vs/pascal-r6kuqfl_.js +1 -0
  89. package/public/cdn/monaco-editor/min/vs/pascaligo-BiXoTmXh.js +1 -0
  90. package/public/cdn/monaco-editor/min/vs/perl-DABw_TcH.js +1 -0
  91. package/public/cdn/monaco-editor/min/vs/pgsql-me_jFXeX.js +1 -0
  92. package/public/cdn/monaco-editor/min/vs/php-D_kh-9LK.js +1 -0
  93. package/public/cdn/monaco-editor/min/vs/pla-VfZjczW0.js +1 -0
  94. package/public/cdn/monaco-editor/min/vs/postiats-BBSzz8Pk.js +1 -0
  95. package/public/cdn/monaco-editor/min/vs/powerquery-Dt-g_2cc.js +1 -0
  96. package/public/cdn/monaco-editor/min/vs/powershell-B-7ap1zc.js +1 -0
  97. package/public/cdn/monaco-editor/min/vs/protobuf-BmtuEB1A.js +2 -0
  98. package/public/cdn/monaco-editor/min/vs/pug-BRpRNeEb.js +1 -0
  99. package/public/cdn/monaco-editor/min/vs/python-Cr0UkIbn.js +1 -0
  100. package/public/cdn/monaco-editor/min/vs/qsharp-BzsFaUU9.js +1 -0
  101. package/public/cdn/monaco-editor/min/vs/r-f8dDdrp4.js +1 -0
  102. package/public/cdn/monaco-editor/min/vs/razor-BYAHOTkz.js +1 -0
  103. package/public/cdn/monaco-editor/min/vs/redis-fvZQY4PI.js +1 -0
  104. package/public/cdn/monaco-editor/min/vs/redshift-45Et0LQi.js +1 -0
  105. package/public/cdn/monaco-editor/min/vs/restructuredtext-C7UUFKFD.js +1 -0
  106. package/public/cdn/monaco-editor/min/vs/ruby-CZO8zYTz.js +1 -0
  107. package/public/cdn/monaco-editor/min/vs/rust-Bfetafyc.js +1 -0
  108. package/public/cdn/monaco-editor/min/vs/sb-3GYllVck.js +1 -0
  109. package/public/cdn/monaco-editor/min/vs/scala-foMgrKo1.js +1 -0
  110. package/public/cdn/monaco-editor/min/vs/scheme-CHdMtr7p.js +1 -0
  111. package/public/cdn/monaco-editor/min/vs/scss-C1cmLt9V.js +3 -0
  112. package/public/cdn/monaco-editor/min/vs/shell-ClXCKCEW.js +1 -0
  113. package/public/cdn/monaco-editor/min/vs/solidity-MZ6ExpPy.js +1 -0
  114. package/public/cdn/monaco-editor/min/vs/sophia-DWkuSsPQ.js +1 -0
  115. package/public/cdn/monaco-editor/min/vs/sparql-AUGFYSyk.js +1 -0
  116. package/public/cdn/monaco-editor/min/vs/sql-32GpJSV2.js +1 -0
  117. package/public/cdn/monaco-editor/min/vs/st-CuDFIVZ_.js +1 -0
  118. package/public/cdn/monaco-editor/min/vs/swift-n-2HociN.js +3 -0
  119. package/public/cdn/monaco-editor/min/vs/systemverilog-Ch4vA8Yt.js +1 -0
  120. package/public/cdn/monaco-editor/min/vs/tcl-D74tq1nH.js +1 -0
  121. package/public/cdn/monaco-editor/min/vs/tsMode-CZz1Umrk.js +11 -0
  122. package/public/cdn/monaco-editor/min/vs/twig-C6taOxMV.js +1 -0
  123. package/public/cdn/monaco-editor/min/vs/typescript-DfOrAzoV.js +1 -0
  124. package/public/cdn/monaco-editor/min/vs/typespec-D-PIh9Xw.js +1 -0
  125. package/public/cdn/monaco-editor/min/vs/vb-Dyb2648j.js +1 -0
  126. package/public/cdn/monaco-editor/min/vs/wgsl-BhLXMOR0.js +298 -0
  127. package/public/cdn/monaco-editor/min/vs/workers-DcJshg-q.js +1 -0
  128. package/public/cdn/monaco-editor/min/vs/xml-CdsdnY8S.js +1 -0
  129. package/public/cdn/monaco-editor/min/vs/yaml-DYGvmE88.js +1 -0
  130. package/public/cdn/vue/vue.global.js +18410 -0
  131. package/public/index.html +429 -0
  132. package/public/manage.html +750 -0
  133. package/public/monaco-editor.html +54 -0
  134. package/public/stylesheets/style.css +8 -0
  135. package/views/error.ejs +3 -0
  136. package/views/index.ejs +11 -0
package/index.js ADDED
@@ -0,0 +1,625 @@
1
+ var express = require('express')
2
+ var path = require('path')
3
+ var httpProxy = require('http-proxy')
4
+ var Mock = require('mockjs')
5
+
6
+ const { createProxyMiddleware, responseInterceptor, fixRequestBody } = require('http-proxy-middleware');
7
+ var debug = require('debug')
8
+ var proxyDebug = debug('rwz-dev-proxy:proxy')
9
+ var app = express()
10
+ var morgan = require('morgan');
11
+ app.use(morgan('short', {
12
+ immediate: true
13
+ }));
14
+ app.use(function (req, res, next) {
15
+ const origin = req.headers && req.headers.origin
16
+ if (origin) {
17
+ res.setHeader('Access-Control-Allow-Origin', origin)
18
+ res.setHeader('Vary', 'Origin')
19
+ res.setHeader('Access-Control-Allow-Credentials', 'true')
20
+ } else {
21
+ res.setHeader('Access-Control-Allow-Origin', '*')
22
+ }
23
+
24
+ const requestHeaders = req.headers && req.headers['access-control-request-headers']
25
+ res.setHeader('Access-Control-Allow-Headers', requestHeaders || 'Content-Type, Authorization, X-Requested-With')
26
+ res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS')
27
+ res.setHeader('Access-Control-Expose-Headers', 'Content-Length, Content-Type')
28
+
29
+ if (req.method === 'OPTIONS') {
30
+ res.statusCode = 204
31
+ return res.end()
32
+ }
33
+ next()
34
+ });
35
+ app.use(express.json());
36
+ app.use(express.urlencoded({ extended: true }));
37
+ var db = require('./db')
38
+
39
+ function isProxyDebugEnabled() {
40
+ return true
41
+ return process.env.RWZ_PROXY_DEBUG === '1' || process.env.PROXY_DEBUG === '1'
42
+ }
43
+
44
+ function logProxy() {
45
+ try {
46
+ proxyDebug.apply(null, arguments)
47
+ } catch (e) {}
48
+ if (!isProxyDebugEnabled()) return
49
+ try {
50
+ console.log.apply(console, ['[proxy]'].concat([].slice.call(arguments)))
51
+ } catch (e) {}
52
+ }
53
+
54
+ function safeJsonParse(str, fallbackValue) {
55
+ try {
56
+ return JSON.parse(str)
57
+ } catch (e) {
58
+ return fallbackValue
59
+ }
60
+ }
61
+
62
+ function normalizePrefix(prefix) {
63
+ if (!prefix) return ''
64
+ if (prefix[0] !== '/') return '/' + prefix
65
+ return prefix
66
+ }
67
+
68
+ function truncateString(str, maxLen) {
69
+ if (typeof str !== 'string') return ''
70
+ if (str.length <= maxLen) return str
71
+ return str.slice(0, maxLen)
72
+ }
73
+
74
+ function executeScript(scriptContent, context) {
75
+ if (!scriptContent || !scriptContent.trim()) return {};
76
+ const module = { exports: {} };
77
+ const { req, res, Mock } = context;
78
+
79
+ try {
80
+ const fn = new Function('module', 'exports', 'require', 'Mock', 'req', 'res', scriptContent);
81
+ const result = fn(module, module.exports, require, Mock, req, res);
82
+
83
+ // Compatibility: if script returns an object/function directly (legacy style), wrap it
84
+ if (result !== undefined && !module.exports.onRequest && !module.exports.onResponse) {
85
+ return { _legacy: result, ...module.exports };
86
+ }
87
+ return module.exports;
88
+ } catch (e) {
89
+ console.error('Script execution error:', e);
90
+ return {};
91
+ }
92
+ }
93
+
94
+ let routeCache = { expiresAt: 0, routes: [] }
95
+ function invalidateRouteCache() {
96
+ routeCache.expiresAt = 0
97
+ }
98
+
99
+ async function ensureDefaultRoute() {
100
+ if (!db.proxyRoute) return
101
+ const count = await db.proxyRoute.count()
102
+ if (count > 0) return
103
+ await db.proxyRoute.create({
104
+ name: 'jeecg-boot',
105
+ enabled: true,
106
+ priority: 0,
107
+ routePrefix: '/jeecg-boot',
108
+ target: 'http://192.168.86.170:8001/mom',
109
+ changeOrigin: true,
110
+ pathRewrite: JSON.stringify({ '^/jeecg-boot': '' }),
111
+ })
112
+ invalidateRouteCache()
113
+ }
114
+
115
+ async function getEnabledRoutes() {
116
+ if (Date.now() < routeCache.expiresAt) return routeCache.routes
117
+ if (!db.proxyRoute) return []
118
+ const routes = await db.proxyRoute.findAll({
119
+ where: { enabled: true },
120
+ order: [['priority', 'DESC'], ['id', 'ASC']],
121
+ })
122
+ routeCache = { expiresAt: Date.now() + 1000, routes }
123
+ return routes
124
+ }
125
+
126
+ app.get('/api/health', function (req, res) {
127
+ res.json({ ok: true })
128
+ })
129
+
130
+ app.get('/api/routes', async function (req, res) {
131
+ const routes = await db.proxyRoute.findAll({ order: [['priority', 'DESC'], ['id', 'ASC']] })
132
+ res.json({ data: routes })
133
+ })
134
+
135
+ app.get('/api/routes/:id', async function (req, res) {
136
+ const id = Number(req.params.id)
137
+ const route = await db.proxyRoute.findByPk(id)
138
+ if (!route) return res.status(404).json({ message: '路由不存在' })
139
+ res.json({ data: route })
140
+ })
141
+
142
+ app.post('/api/routes', async function (req, res) {
143
+ const body = req.body || {}
144
+ const routePrefix = normalizePrefix(body.routePrefix || '')
145
+ const target = (body.target || '').trim()
146
+ if (!routePrefix || !target) {
147
+ return res.status(400).json({ message: 'routePrefix 和 target 不能为空' })
148
+ }
149
+ const created = await db.proxyRoute.create({
150
+ name: (body.name || routePrefix).trim(),
151
+ enabled: body.enabled !== false,
152
+ priority: Number.isFinite(Number(body.priority)) ? Number(body.priority) : 0,
153
+ routePrefix,
154
+ target,
155
+ changeOrigin: body.changeOrigin !== false,
156
+ pathRewrite: typeof body.pathRewrite === 'string' ? body.pathRewrite : JSON.stringify(body.pathRewrite || {}),
157
+ })
158
+ invalidateRouteCache()
159
+ res.json({ data: created })
160
+ })
161
+
162
+ app.put('/api/routes/:id', async function (req, res) {
163
+ const id = Number(req.params.id)
164
+ const body = req.body || {}
165
+ const route = await db.proxyRoute.findByPk(id)
166
+ if (!route) return res.status(404).json({ message: '路由不存在' })
167
+
168
+ const routePrefix = normalizePrefix(body.routePrefix ?? route.routePrefix)
169
+ const target = (body.target ?? route.target).trim()
170
+ if (!routePrefix || !target) {
171
+ return res.status(400).json({ message: 'routePrefix 和 target 不能为空' })
172
+ }
173
+
174
+ await route.update({
175
+ name: (body.name ?? route.name).trim(),
176
+ enabled: body.enabled ?? route.enabled,
177
+ priority: Number.isFinite(Number(body.priority)) ? Number(body.priority) : route.priority,
178
+ routePrefix,
179
+ target,
180
+ changeOrigin: body.changeOrigin ?? route.changeOrigin,
181
+ pathRewrite: typeof body.pathRewrite === 'string' ? body.pathRewrite : JSON.stringify(body.pathRewrite ?? safeJsonParse(route.pathRewrite, {})),
182
+ })
183
+ invalidateRouteCache()
184
+ res.json({ data: route })
185
+ })
186
+
187
+ app.delete('/api/routes/:id', async function (req, res) {
188
+ const id = Number(req.params.id)
189
+ const route = await db.proxyRoute.findByPk(id)
190
+ if (!route) return res.status(404).json({ message: '路由不存在' })
191
+ await route.destroy()
192
+ invalidateRouteCache()
193
+ res.json({ ok: true })
194
+ })
195
+
196
+ app.get('/api/logs', async function (req, res) {
197
+ const limit = Math.min(200, Math.max(1, Number(req.query.limit) || 50))
198
+ const logs = await db.proxyLog.findAll({ order: [['id', 'DESC']], limit })
199
+ res.json({ data: logs })
200
+ })
201
+
202
+ app.get('/api/routes/:id/interfaces', async function (req, res) {
203
+ const routeId = Number(req.params.id)
204
+ const interfaces = await db.proxyInterface.findAll({
205
+ where: { routeId },
206
+ order: [['pathname', 'ASC'], ['method', 'ASC']]
207
+ })
208
+ res.json({ data: interfaces })
209
+ })
210
+
211
+ app.put('/api/interfaces/:id', async function (req, res) {
212
+ const id = Number(req.params.id)
213
+ const body = req.body || {}
214
+ const iface = await db.proxyInterface.findByPk(id)
215
+ if (!iface) return res.status(404).json({ message: '接口不存在' })
216
+
217
+ const updateData = {
218
+ description: body.description,
219
+ }
220
+ if (body.reqHeaders !== undefined) updateData.reqHeaders = body.reqHeaders
221
+ if (body.reqQuery !== undefined) updateData.reqQuery = body.reqQuery
222
+ if (body.reqBody !== undefined) updateData.reqBody = body.reqBody
223
+ if (body.resBody !== undefined) updateData.resBody = body.resBody
224
+ if (body.mode !== undefined) updateData.mode = body.mode
225
+ if (body.staticData !== undefined) updateData.staticData = body.staticData
226
+ if (body.mockData !== undefined) updateData.mockData = body.mockData
227
+ if (body.mockResult !== undefined) updateData.mockResult = body.mockResult
228
+ if (body.mockScript !== undefined) updateData.mockScript = body.mockScript
229
+
230
+ // Debug log
231
+ console.log('Updating interface:', id, 'staticData present:', body.staticData !== undefined, 'Length:', body.staticData ? body.staticData.length : 0)
232
+
233
+ await iface.update(updateData)
234
+ res.json({ data: iface })
235
+ })
236
+
237
+ app.delete('/api/interfaces/:id', async function (req, res) {
238
+ const id = Number(req.params.id)
239
+ const iface = await db.proxyInterface.findByPk(id)
240
+ if (!iface) return res.status(404).json({ message: '接口不存在' })
241
+ await iface.destroy()
242
+ res.json({ ok: true })
243
+ })
244
+
245
+ app.use(async function (req, res, next) {
246
+ try {
247
+ logProxy('incoming', {
248
+ method: req.method,
249
+ path: req.path,
250
+ url: req.url,
251
+ host: req.headers && req.headers.host,
252
+ })
253
+
254
+ if (req.path === '/api' || req.path.indexOf('/api/') === 0) {
255
+ logProxy('skip /api', { path: req.path })
256
+ return next()
257
+ }
258
+
259
+ let hookModule = {}
260
+ const routes = await getEnabledRoutes()
261
+ logProxy('routes loaded', { count: routes.length, prefixes: routes.map(r => r.routePrefix).slice(0, 20) })
262
+ const hit = routes.find(r => req.path === r.routePrefix || req.path.indexOf(r.routePrefix + '/') === 0)
263
+ if (!hit) {
264
+ logProxy('no route hit', { path: req.path })
265
+ return next()
266
+ }
267
+
268
+ if (db && db.proxyInterface) {
269
+ const pathname = req._parsedUrl && req._parsedUrl.pathname
270
+ console.log('DEBUG: Finding iface', hit.id, req.method, pathname);
271
+ if (pathname) {
272
+ const iface = await db.proxyInterface.findOne({
273
+ where: {
274
+ routeId: hit.id,
275
+ method: req.method,
276
+ pathname: pathname
277
+ }
278
+ })
279
+ if (iface) {
280
+ console.log('DEBUG: Found iface', iface.id, 'mockScript len:', iface.mockScript ? iface.mockScript.length : 0);
281
+ // Load hooks
282
+ if (iface.mockScript) {
283
+ hookModule = executeScript(iface.mockScript, { req, res, Mock });
284
+ console.log('DEBUG: Hook module keys', Object.keys(hookModule));
285
+ if (hookModule.onRequest) {
286
+ await hookModule.onRequest({ req, res, Mock });
287
+ if (res.headersSent) return;
288
+ }
289
+ }
290
+ if (iface.mode === 'static') {
291
+ logProxy('static hit', { pathname })
292
+ res.status(iface.statusCode || 200)
293
+
294
+ let body
295
+ try {
296
+ // Prefer staticData, fallback to resBody ONLY if staticData is null (not set)
297
+ let content = iface.staticData
298
+ if (content === null || content === undefined) {
299
+ content = iface.resBody || '{}'
300
+ }
301
+ body = JSON.parse(content)
302
+ } catch (e) {
303
+ // If JSON parse fails, use as raw string
304
+ let content = iface.staticData
305
+ if (content === null || content === undefined) {
306
+ content = iface.resBody || ''
307
+ }
308
+ body = content
309
+ }
310
+
311
+ if (hookModule.onResponse) {
312
+ try {
313
+ console.log('DEBUG: calling Static onResponse', body);
314
+ const newBody = await hookModule.onResponse({ body, req, res, statusCode: iface.statusCode || 200, Mock });
315
+ console.log('DEBUG: Static onResponse result', newBody);
316
+ if (newBody !== undefined) body = newBody;
317
+ } catch(e) { console.error('Static onResponse error', e); }
318
+ }
319
+
320
+ if (typeof body === 'object') {
321
+ res.json(body)
322
+ } else {
323
+ res.send(body)
324
+ }
325
+ return
326
+ } else if (iface.mode === 'mock') {
327
+ logProxy('mock hit', { pathname })
328
+ try {
329
+ let mockTemplate = {}
330
+
331
+ if (iface.mockScript && iface.mockScript.trim()) {
332
+ // Use pre-loaded hookModule to avoid double execution
333
+ if (hookModule._legacy) {
334
+ mockTemplate = hookModule._legacy;
335
+ } else {
336
+ // If hookModule has other exports besides hooks, use them as template
337
+ // Otherwise fallback to mockData
338
+ const dataKeys = Object.keys(hookModule).filter(k => k !== 'onRequest' && k !== 'onResponse');
339
+ if (dataKeys.length > 0) {
340
+ mockTemplate = { ...hookModule };
341
+ delete mockTemplate.onRequest;
342
+ delete mockTemplate.onResponse;
343
+ } else {
344
+ mockTemplate = safeJsonParse(iface.mockData, {})
345
+ }
346
+ }
347
+
348
+ // Support beforeMockData if present (legacy support?)
349
+ // Since we are using module.exports style, beforeMockData should be an export if needed
350
+ if (mockTemplate && typeof mockTemplate === 'object' && typeof mockTemplate.beforeMockData === 'function') {
351
+ const contextReq = {
352
+ query: req.query,
353
+ body: req.body,
354
+ headers: req.headers,
355
+ params: req.params,
356
+ path: req.path,
357
+ url: req.url,
358
+ method: req.method
359
+ }
360
+ mockTemplate = mockTemplate.beforeMockData({ req: contextReq, Mock })
361
+ }
362
+ } else {
363
+ mockTemplate = safeJsonParse(iface.mockData, {})
364
+ }
365
+
366
+ const mockResult = Mock.mock(mockTemplate)
367
+
368
+ let finalBody = mockResult
369
+ if (hookModule.onResponse) {
370
+ try {
371
+ console.log('DEBUG: calling Mock onResponse', finalBody);
372
+ const newBody = await hookModule.onResponse({ body: finalBody, req, res, statusCode: 200, Mock });
373
+ console.log('DEBUG: Mock onResponse result', newBody);
374
+ if (newBody !== undefined) finalBody = newBody;
375
+ } catch(e) { console.error('Mock onResponse error', e); }
376
+ }
377
+
378
+ res.json(finalBody)
379
+ } catch (e) {
380
+ res.status(500).json({ message: 'Mock Error', error: e.message })
381
+ }
382
+ return
383
+ }
384
+ }
385
+ }
386
+ }
387
+
388
+ const pathRewriteObj = safeJsonParse(hit.pathRewrite, {})
389
+ const changeOrigin = hit.changeOrigin !== false
390
+
391
+ logProxy('route hit', {
392
+ routeId: hit.id,
393
+ routePrefix: hit.routePrefix,
394
+ target: hit.target,
395
+ changeOrigin,
396
+ pathRewrite: pathRewriteObj,
397
+ })
398
+
399
+ return createProxyMiddleware({
400
+ target: hit.target,
401
+ changeOrigin,
402
+ pathRewrite: pathRewriteObj,
403
+ timeout: 30000,
404
+ proxyTimeout: 30000,
405
+ selfHandleResponse: true,
406
+ on: {
407
+ error: function (err, req, res, target) {
408
+ logProxy('proxy error', {
409
+ message: err && err.message,
410
+ code: err && err.code,
411
+ target,
412
+ method: req && req.method,
413
+ url: req && req.url,
414
+ })
415
+ if (res && !res.headersSent) {
416
+ res.statusCode = 502
417
+ res.setHeader('content-type', 'application/json; charset=utf-8')
418
+ }
419
+ try {
420
+ if (res && !res.writableEnded) {
421
+ res.end(JSON.stringify({ message: '代理请求失败', error: (err && err.message) || 'unknown' }))
422
+ }
423
+ } catch (e) {}
424
+ },
425
+ proxyReq: function (proxyReq, req, res) {
426
+ // 禁用压缩,以便我们可以读取响应内容
427
+ try {
428
+ proxyReq.removeHeader('accept-encoding')
429
+ proxyReq.removeHeader('Accept-Encoding')
430
+ } catch(e) {}
431
+
432
+ try {
433
+ fixRequestBody(proxyReq, req, res)
434
+ } catch (e) {
435
+ logProxy('fixRequestBody error', { message: e && e.message })
436
+ }
437
+ try {
438
+ logProxy('proxy req', {
439
+ method: req.method,
440
+ url: req.url,
441
+ target: hit.target,
442
+ proxyPath: proxyReq && proxyReq.path,
443
+ proxyHost: proxyReq && proxyReq.getHeader && proxyReq.getHeader('host'),
444
+ })
445
+ } catch (e) {
446
+ console.log(e)
447
+ }
448
+ },
449
+ proxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
450
+ const contentType = (proxyRes.headers && (proxyRes.headers['content-type'] || proxyRes.headers['Content-Type'])) || ''
451
+ const contentEncoding = (proxyRes.headers && (proxyRes.headers['content-encoding'] || proxyRes.headers['Content-Encoding'])) || ''
452
+ const isText = String(contentType).indexOf('text/') === 0 || String(contentType).indexOf('application/json') >= 0 || String(contentType).indexOf('application/javascript') >= 0
453
+ const isCompressed = !!contentEncoding
454
+
455
+ let responseText = ''
456
+ try {
457
+ if (isText && isCompressed) {
458
+ // 如果是压缩的响应,目前没有解压逻辑,这里直接转字符串可能乱码
459
+ // 暂时不处理压缩响应的文本读取,或者提示不支持
460
+ } else if (isText) {
461
+ responseText = responseBuffer.toString('utf8')
462
+
463
+ // onResponse hook for Proxy
464
+ if (hookModule.onResponse) {
465
+ try {
466
+ let body = responseText;
467
+ try { body = JSON.parse(responseText); } catch(e) {}
468
+
469
+ const newBody = await hookModule.onResponse({ body, req, res, statusCode: proxyRes.statusCode, Mock });
470
+
471
+ if (newBody !== undefined) {
472
+ if (typeof newBody === 'object') {
473
+ responseText = JSON.stringify(newBody);
474
+ } else {
475
+ responseText = String(newBody);
476
+ }
477
+ responseBuffer = Buffer.from(responseText);
478
+ }
479
+ } catch(e) { console.error('Proxy onResponse hook error', e) }
480
+ }
481
+ }
482
+ } catch(e) {}
483
+
484
+ const bodyText = req.body ? truncateString(JSON.stringify(req.body), 20000) : ''
485
+ logProxy('proxy res', {
486
+ routeId: hit.id,
487
+ statusCode: proxyRes.statusCode,
488
+ contentType,
489
+ contentEncoding,
490
+ isText,
491
+ bytes: responseBuffer && responseBuffer.length,
492
+ })
493
+ try {
494
+ (async () => {
495
+ try {
496
+ if (db && db.proxyLog) {
497
+ await db.proxyLog.create({
498
+ routeId: hit.id,
499
+ method: req.method,
500
+ pathname: req._parsedUrl && req._parsedUrl.pathname,
501
+ query: req._parsedUrl && req._parsedUrl.query,
502
+ url: req.url,
503
+ body: bodyText,
504
+ target: hit.target,
505
+ statusCode: proxyRes.statusCode,
506
+ response: truncateString(responseText, 20000),
507
+ })
508
+ }
509
+ if (db && db.proxyInterface) {
510
+ const pathname = req._parsedUrl && req._parsedUrl.pathname
511
+ if (pathname) {
512
+ const [iface, created] = await db.proxyInterface.findOrCreate({
513
+ where: {
514
+ routeId: hit.id,
515
+ method: req.method,
516
+ pathname: pathname
517
+ },
518
+ defaults: {
519
+ reqQuery: req._parsedUrl.query,
520
+ reqHeaders: JSON.stringify(req.headers),
521
+ reqBody: bodyText,
522
+ resBody: truncateString(responseText, 20000),
523
+ statusCode: proxyRes.statusCode
524
+ }
525
+ })
526
+ if (!created && iface.mode && iface.mode !== 'proxy') {
527
+ // 非代理模式下不更新接口数据
528
+ } else if (!created && proxyRes.statusCode >= 200 && proxyRes.statusCode < 300) {
529
+ await iface.update({
530
+ reqQuery: req._parsedUrl.query,
531
+ reqHeaders: JSON.stringify(req.headers),
532
+ reqBody: bodyText,
533
+ resBody: truncateString(responseText, 20000),
534
+ statusCode: proxyRes.statusCode
535
+ })
536
+ } else if (!created && (!iface.resBody || iface.statusCode >= 400)) {
537
+ // 如果之前记录的是失败的,或者没有记录过返回结果,这次虽然不是200-299,但也更新一下,保留最后一次状态
538
+ await iface.update({
539
+ reqQuery: req._parsedUrl.query,
540
+ reqHeaders: JSON.stringify(req.headers),
541
+ reqBody: bodyText,
542
+ resBody: truncateString(responseText, 20000),
543
+ statusCode: proxyRes.statusCode
544
+ })
545
+ }
546
+ }
547
+ }
548
+ } catch (e) {
549
+ logProxy('db record error', e && e.message)
550
+ }
551
+ })()
552
+ } catch (e) {}
553
+
554
+ // 强制返回 Buffer 以避免 responseInterceptor 内部错误
555
+ return responseBuffer
556
+ }),
557
+ }
558
+ }).call(this, req, res, next)
559
+ } catch (e) {
560
+ logProxy('middleware error', { message: e && e.message, stack: e && e.stack })
561
+ next(e)
562
+ }
563
+ })
564
+
565
+ app.use(express.static(path.join(__dirname, 'public')))
566
+
567
+ app.get('*', function (req, res) {
568
+ res.sendFile(path.join(__dirname, 'public', 'index.html'))
569
+ })
570
+
571
+ const port = Number(process.env.PORT) || 8081
572
+ const server = app.listen(port, function () {
573
+ console.log('listen port' + port, 'http://localhost:' + port)
574
+ })
575
+
576
+ server.on('upgrade', async function (req, socket, head) {
577
+ try {
578
+ const urlObj = new URL(req.url, 'http://localhost')
579
+ const pathname = urlObj.pathname
580
+
581
+ const routes = await getEnabledRoutes()
582
+ const hit = routes.find(r => pathname === r.routePrefix || pathname.indexOf(r.routePrefix + '/') === 0)
583
+
584
+ if (!hit) {
585
+ socket.destroy()
586
+ return
587
+ }
588
+
589
+ const pathRewriteObj = safeJsonParse(hit.pathRewrite, {})
590
+ const changeOrigin = hit.changeOrigin !== false
591
+
592
+ let targetPath = req.url
593
+ for (const key in pathRewriteObj) {
594
+ const regex = new RegExp(key)
595
+ if (regex.test(targetPath)) {
596
+ targetPath = targetPath.replace(regex, pathRewriteObj[key])
597
+ break
598
+ }
599
+ }
600
+ req.url = targetPath
601
+
602
+ const proxy = httpProxy.createProxyServer({
603
+ target: hit.target,
604
+ changeOrigin,
605
+ ws: true,
606
+ secure: false
607
+ })
608
+
609
+ proxy.on('error', function (err) {
610
+ console.error('ws proxy error', err)
611
+ socket.end()
612
+ })
613
+
614
+ proxy.ws(req, socket, head)
615
+ } catch (e) {
616
+ console.error(e)
617
+ socket.destroy()
618
+ }
619
+ })
620
+
621
+ if (db.ready && typeof db.ready.then === 'function') {
622
+ db.ready.then(ensureDefaultRoute).catch(function () {})
623
+ } else {
624
+ ensureDefaultRoute()
625
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "rwz-dev-proxy",
3
+ "version": "1.0.0",
4
+ "description": "A development proxy server",
5
+ "bin": {
6
+ "rwz-dev-proxy": "./bin/rwz-dev-proxy.js"
7
+ },
8
+ "files": [
9
+ "bin",
10
+ "public",
11
+ "views",
12
+ "index.js",
13
+ "db.js"
14
+ ],
15
+ "scripts": {
16
+ "start": "node ./bin/rwz-dev-proxy.js",
17
+ "dev": "supervisor -i node_modules ./index.js",
18
+ "proxy": "node ./index.js"
19
+ },
20
+ "dependencies": {
21
+ "cookie-parser": "~1.4.4",
22
+ "debug": "~2.6.9",
23
+ "ejs": "~2.6.1",
24
+ "express": "~4.16.1",
25
+ "http-errors": "~1.6.3",
26
+ "http-proxy-middleware": "^3.0.5",
27
+ "mockjs": "^1.1.0",
28
+ "morgan": "~1.9.1",
29
+ "sequelize": "^6.16.0",
30
+ "sqlite3": "^5.0.2"
31
+ }
32
+ }