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.
- package/bin/rwz-dev-proxy.js +29 -0
- package/db.js +121 -0
- package/index.js +625 -0
- package/package.json +32 -0
- package/public/cdn/axios/dist/axios.min.js +3 -0
- package/public/cdn/element-plus/dist/index.css +1 -0
- package/public/cdn/element-plus/dist/index.full.js +72239 -0
- package/public/cdn/mockjs/dist/mock-min.js +10 -0
- package/public/cdn/monaco-editor/min/vs/_commonjsHelpers-CT9FvmAN.js +1 -0
- package/public/cdn/monaco-editor/min/vs/abap-D-t0cyap.js +1 -0
- package/public/cdn/monaco-editor/min/vs/apex-CcIm7xu6.js +1 -0
- package/public/cdn/monaco-editor/min/vs/assets/css.worker-HnVq6Ewq.js +93 -0
- package/public/cdn/monaco-editor/min/vs/assets/editor.worker-Be8ye1pW.js +26 -0
- package/public/cdn/monaco-editor/min/vs/assets/html.worker-B51mlPHg.js +470 -0
- package/public/cdn/monaco-editor/min/vs/assets/json.worker-DKiEKt88.js +58 -0
- package/public/cdn/monaco-editor/min/vs/assets/ts.worker-CMbG-7ft.js +67731 -0
- package/public/cdn/monaco-editor/min/vs/azcli-BA0tQDCg.js +1 -0
- package/public/cdn/monaco-editor/min/vs/basic-languages/monaco.contribution.js +1 -0
- package/public/cdn/monaco-editor/min/vs/bat-C397hTD6.js +1 -0
- package/public/cdn/monaco-editor/min/vs/bicep-DF5aW17k.js +2 -0
- package/public/cdn/monaco-editor/min/vs/cameligo-plsz8qhj.js +1 -0
- package/public/cdn/monaco-editor/min/vs/clojure-Y2auQMzK.js +1 -0
- package/public/cdn/monaco-editor/min/vs/coffee-Bu45yuWE.js +1 -0
- package/public/cdn/monaco-editor/min/vs/cpp-CkKPQIni.js +1 -0
- package/public/cdn/monaco-editor/min/vs/csharp-CX28MZyh.js +1 -0
- package/public/cdn/monaco-editor/min/vs/csp-D8uWnyxW.js +1 -0
- package/public/cdn/monaco-editor/min/vs/css-CaeNmE3S.js +3 -0
- package/public/cdn/monaco-editor/min/vs/cssMode-CjiAH6dQ.js +1 -0
- package/public/cdn/monaco-editor/min/vs/cypher-DVThT8BS.js +1 -0
- package/public/cdn/monaco-editor/min/vs/dart-CmGfCvrO.js +1 -0
- package/public/cdn/monaco-editor/min/vs/dockerfile-CZqqYdch.js +1 -0
- package/public/cdn/monaco-editor/min/vs/ecl-30fUercY.js +1 -0
- package/public/cdn/monaco-editor/min/vs/editor/editor.main.css +1 -0
- package/public/cdn/monaco-editor/min/vs/editor/editor.main.js +5 -0
- package/public/cdn/monaco-editor/min/vs/editor.api-CalNCsUg.js +903 -0
- package/public/cdn/monaco-editor/min/vs/elixir-xjPaIfzF.js +1 -0
- package/public/cdn/monaco-editor/min/vs/flow9-DqtmStfK.js +1 -0
- package/public/cdn/monaco-editor/min/vs/freemarker2-Cz_sV6Md.js +3 -0
- package/public/cdn/monaco-editor/min/vs/fsharp-BOMdg4U1.js +1 -0
- package/public/cdn/monaco-editor/min/vs/go-D_hbi-Jt.js +1 -0
- package/public/cdn/monaco-editor/min/vs/graphql-CKUU4kLG.js +1 -0
- package/public/cdn/monaco-editor/min/vs/handlebars-OwglfO-1.js +1 -0
- package/public/cdn/monaco-editor/min/vs/hcl-DTaboeZW.js +1 -0
- package/public/cdn/monaco-editor/min/vs/html-Pa1xEWsY.js +1 -0
- package/public/cdn/monaco-editor/min/vs/htmlMode-Bz67EXwp.js +1 -0
- package/public/cdn/monaco-editor/min/vs/ini-CsNwO04R.js +1 -0
- package/public/cdn/monaco-editor/min/vs/java-CI4ZMsH9.js +1 -0
- package/public/cdn/monaco-editor/min/vs/javascript-PczUCGdz.js +1 -0
- package/public/cdn/monaco-editor/min/vs/jsonMode-DULH5oaX.js +7 -0
- package/public/cdn/monaco-editor/min/vs/julia-BwzEvaQw.js +1 -0
- package/public/cdn/monaco-editor/min/vs/kotlin-IUYPiTV8.js +1 -0
- package/public/cdn/monaco-editor/min/vs/language/css/monaco.contribution.js +1 -0
- package/public/cdn/monaco-editor/min/vs/language/html/monaco.contribution.js +1 -0
- package/public/cdn/monaco-editor/min/vs/language/json/monaco.contribution.js +1 -0
- package/public/cdn/monaco-editor/min/vs/language/typescript/monaco.contribution.js +1 -0
- package/public/cdn/monaco-editor/min/vs/less-C0eDYdqa.js +2 -0
- package/public/cdn/monaco-editor/min/vs/lexon-iON-Kj97.js +1 -0
- package/public/cdn/monaco-editor/min/vs/liquid-DqKjdPGy.js +1 -0
- package/public/cdn/monaco-editor/min/vs/loader.js +1368 -0
- package/public/cdn/monaco-editor/min/vs/lspLanguageFeatures-kM9O9rjY.js +4 -0
- package/public/cdn/monaco-editor/min/vs/lua-DtygF91M.js +1 -0
- package/public/cdn/monaco-editor/min/vs/m3-CsR4AuFi.js +1 -0
- package/public/cdn/monaco-editor/min/vs/markdown-C_rD0bIw.js +1 -0
- package/public/cdn/monaco-editor/min/vs/mdx-DEWtB1K5.js +1 -0
- package/public/cdn/monaco-editor/min/vs/mips-CiYP61RB.js +1 -0
- package/public/cdn/monaco-editor/min/vs/monaco.contribution-D2OdxNBt.js +1 -0
- package/public/cdn/monaco-editor/min/vs/monaco.contribution-DO3azKX8.js +1 -0
- package/public/cdn/monaco-editor/min/vs/monaco.contribution-EcChJV6a.js +1 -0
- package/public/cdn/monaco-editor/min/vs/monaco.contribution-qLAYrEOP.js +1 -0
- package/public/cdn/monaco-editor/min/vs/msdax-C38-sJlp.js +1 -0
- package/public/cdn/monaco-editor/min/vs/mysql-CdtbpvbG.js +1 -0
- package/public/cdn/monaco-editor/min/vs/nls.messages-loader.js +1 -0
- package/public/cdn/monaco-editor/min/vs/nls.messages.cs.js.js +17 -0
- package/public/cdn/monaco-editor/min/vs/nls.messages.de.js.js +17 -0
- package/public/cdn/monaco-editor/min/vs/nls.messages.es.js.js +17 -0
- package/public/cdn/monaco-editor/min/vs/nls.messages.fr.js.js +15 -0
- package/public/cdn/monaco-editor/min/vs/nls.messages.it.js.js +15 -0
- package/public/cdn/monaco-editor/min/vs/nls.messages.ja.js.js +17 -0
- package/public/cdn/monaco-editor/min/vs/nls.messages.js.js +10 -0
- package/public/cdn/monaco-editor/min/vs/nls.messages.ko.js.js +25 -0
- package/public/cdn/monaco-editor/min/vs/nls.messages.pl.js.js +17 -0
- package/public/cdn/monaco-editor/min/vs/nls.messages.pt-br.js.js +6 -0
- package/public/cdn/monaco-editor/min/vs/nls.messages.ru.js.js +17 -0
- package/public/cdn/monaco-editor/min/vs/nls.messages.tr.js.js +15 -0
- package/public/cdn/monaco-editor/min/vs/nls.messages.zh-cn.js.js +17 -0
- package/public/cdn/monaco-editor/min/vs/nls.messages.zh-tw.js.js +15 -0
- package/public/cdn/monaco-editor/min/vs/objective-c-CntZFaHX.js +1 -0
- package/public/cdn/monaco-editor/min/vs/pascal-r6kuqfl_.js +1 -0
- package/public/cdn/monaco-editor/min/vs/pascaligo-BiXoTmXh.js +1 -0
- package/public/cdn/monaco-editor/min/vs/perl-DABw_TcH.js +1 -0
- package/public/cdn/monaco-editor/min/vs/pgsql-me_jFXeX.js +1 -0
- package/public/cdn/monaco-editor/min/vs/php-D_kh-9LK.js +1 -0
- package/public/cdn/monaco-editor/min/vs/pla-VfZjczW0.js +1 -0
- package/public/cdn/monaco-editor/min/vs/postiats-BBSzz8Pk.js +1 -0
- package/public/cdn/monaco-editor/min/vs/powerquery-Dt-g_2cc.js +1 -0
- package/public/cdn/monaco-editor/min/vs/powershell-B-7ap1zc.js +1 -0
- package/public/cdn/monaco-editor/min/vs/protobuf-BmtuEB1A.js +2 -0
- package/public/cdn/monaco-editor/min/vs/pug-BRpRNeEb.js +1 -0
- package/public/cdn/monaco-editor/min/vs/python-Cr0UkIbn.js +1 -0
- package/public/cdn/monaco-editor/min/vs/qsharp-BzsFaUU9.js +1 -0
- package/public/cdn/monaco-editor/min/vs/r-f8dDdrp4.js +1 -0
- package/public/cdn/monaco-editor/min/vs/razor-BYAHOTkz.js +1 -0
- package/public/cdn/monaco-editor/min/vs/redis-fvZQY4PI.js +1 -0
- package/public/cdn/monaco-editor/min/vs/redshift-45Et0LQi.js +1 -0
- package/public/cdn/monaco-editor/min/vs/restructuredtext-C7UUFKFD.js +1 -0
- package/public/cdn/monaco-editor/min/vs/ruby-CZO8zYTz.js +1 -0
- package/public/cdn/monaco-editor/min/vs/rust-Bfetafyc.js +1 -0
- package/public/cdn/monaco-editor/min/vs/sb-3GYllVck.js +1 -0
- package/public/cdn/monaco-editor/min/vs/scala-foMgrKo1.js +1 -0
- package/public/cdn/monaco-editor/min/vs/scheme-CHdMtr7p.js +1 -0
- package/public/cdn/monaco-editor/min/vs/scss-C1cmLt9V.js +3 -0
- package/public/cdn/monaco-editor/min/vs/shell-ClXCKCEW.js +1 -0
- package/public/cdn/monaco-editor/min/vs/solidity-MZ6ExpPy.js +1 -0
- package/public/cdn/monaco-editor/min/vs/sophia-DWkuSsPQ.js +1 -0
- package/public/cdn/monaco-editor/min/vs/sparql-AUGFYSyk.js +1 -0
- package/public/cdn/monaco-editor/min/vs/sql-32GpJSV2.js +1 -0
- package/public/cdn/monaco-editor/min/vs/st-CuDFIVZ_.js +1 -0
- package/public/cdn/monaco-editor/min/vs/swift-n-2HociN.js +3 -0
- package/public/cdn/monaco-editor/min/vs/systemverilog-Ch4vA8Yt.js +1 -0
- package/public/cdn/monaco-editor/min/vs/tcl-D74tq1nH.js +1 -0
- package/public/cdn/monaco-editor/min/vs/tsMode-CZz1Umrk.js +11 -0
- package/public/cdn/monaco-editor/min/vs/twig-C6taOxMV.js +1 -0
- package/public/cdn/monaco-editor/min/vs/typescript-DfOrAzoV.js +1 -0
- package/public/cdn/monaco-editor/min/vs/typespec-D-PIh9Xw.js +1 -0
- package/public/cdn/monaco-editor/min/vs/vb-Dyb2648j.js +1 -0
- package/public/cdn/monaco-editor/min/vs/wgsl-BhLXMOR0.js +298 -0
- package/public/cdn/monaco-editor/min/vs/workers-DcJshg-q.js +1 -0
- package/public/cdn/monaco-editor/min/vs/xml-CdsdnY8S.js +1 -0
- package/public/cdn/monaco-editor/min/vs/yaml-DYGvmE88.js +1 -0
- package/public/cdn/vue/vue.global.js +18410 -0
- package/public/index.html +429 -0
- package/public/manage.html +750 -0
- package/public/monaco-editor.html +54 -0
- package/public/stylesheets/style.css +8 -0
- package/views/error.ejs +3 -0
- 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
|
+
}
|