@jonathanleelx/meddle 0.5.0-beta.9 → 0.5.1
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/CONFIG_STRUCTURE.md +9 -2
- package/README.md +18 -0
- package/bin/commands/mock/add.js +59 -21
- package/bin/commands/mock/delete.js +16 -24
- package/bin/commands/mock/index.js +59 -25
- package/bin/commands/mock/list.js +35 -18
- package/bin/commands/mock/toggle.js +17 -27
- package/bin/commands/mock/update.js +39 -30
- package/bin/commands/route/active.js +20 -3
- package/bin/commands/route/add.js +113 -94
- package/bin/commands/route/create.js +8 -1
- package/bin/commands/route/delete.js +87 -53
- package/bin/commands/route/index.js +74 -26
- package/bin/commands/route/list.js +2 -2
- package/bin/commands/route/preview.js +2 -2
- package/bin/commands/route/show.js +12 -3
- package/bin/commands/route/update.js +135 -92
- package/bin/commands/status.js +27 -11
- package/bin/commands/update.js +4 -0
- package/bin/doctor.js +8 -4
- package/bin/index.js +11 -7
- package/bin/lib/api-client.js +14 -2
- package/bin/lib/file-access.js +234 -35
- package/bin/lib/meddle-home.js +14 -1
- package/bin/lib/output.js +30 -4
- package/bin/lib/proxy-detect.js +99 -20
- package/bin/lib/proxy-or-file.js +93 -0
- package/bin/lib/route-write-args.js +150 -0
- package/bin/lib/rule-file-edit.js +263 -0
- package/bin/lib/ruleset-readonly.js +61 -0
- package/bin/lib/update-check.js +67 -6
- package/dist/cert.d.ts +2 -0
- package/dist/cert.d.ts.map +1 -1
- package/dist/cert.js +53 -1
- package/dist/cert.js.map +1 -1
- package/dist/core/browser.d.ts +10 -1
- package/dist/core/browser.d.ts.map +1 -1
- package/dist/core/browser.js +252 -36
- package/dist/core/browser.js.map +1 -1
- package/dist/core/config-diagnostics.d.ts.map +1 -1
- package/dist/core/config-diagnostics.js +6 -1
- package/dist/core/config-diagnostics.js.map +1 -1
- package/dist/core/custom-plugin-loader.d.ts.map +1 -1
- package/dist/core/custom-plugin-loader.js +2 -0
- package/dist/core/custom-plugin-loader.js.map +1 -1
- package/dist/core/mock-handler.d.ts +63 -0
- package/dist/core/mock-handler.d.ts.map +1 -1
- package/dist/core/mock-handler.js +641 -29
- package/dist/core/mock-handler.js.map +1 -1
- package/dist/core/mock-utils.d.ts +58 -0
- package/dist/core/mock-utils.d.ts.map +1 -0
- package/dist/core/mock-utils.js +167 -0
- package/dist/core/mock-utils.js.map +1 -0
- package/dist/core/plugin-bootstrap-runner.d.ts +1 -0
- package/dist/core/plugin-bootstrap-runner.d.ts.map +1 -1
- package/dist/core/plugin-bootstrap-runner.js +37 -1
- package/dist/core/plugin-bootstrap-runner.js.map +1 -1
- package/dist/core/plugin-source.d.ts +24 -0
- package/dist/core/plugin-source.d.ts.map +1 -0
- package/dist/core/plugin-source.js +47 -0
- package/dist/core/plugin-source.js.map +1 -0
- package/dist/core/proxy-record.d.ts +14 -0
- package/dist/core/proxy-record.d.ts.map +1 -1
- package/dist/core/proxy-record.js +39 -0
- package/dist/core/proxy-record.js.map +1 -1
- package/dist/core/rule-file-edit.d.ts +81 -0
- package/dist/core/rule-file-edit.d.ts.map +1 -0
- package/dist/core/rule-file-edit.js +243 -0
- package/dist/core/rule-file-edit.js.map +1 -0
- package/dist/core/static-server.d.ts.map +1 -1
- package/dist/core/static-server.js +15 -13
- package/dist/core/static-server.js.map +1 -1
- package/dist/core/types.d.ts +15 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/plugins/builtin/mock-plugin.d.ts.map +1 -1
- package/dist/plugins/builtin/mock-plugin.js +6 -5
- package/dist/plugins/builtin/mock-plugin.js.map +1 -1
- package/dist/server/agent/tools.d.ts.map +1 -1
- package/dist/server/agent/tools.js +24 -18
- package/dist/server/agent/tools.js.map +1 -1
- package/dist/server/app-info.d.ts +33 -0
- package/dist/server/app-info.d.ts.map +1 -0
- package/dist/server/app-info.js +116 -0
- package/dist/server/app-info.js.map +1 -0
- package/dist/server/config.d.ts +1 -0
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js +114 -16
- package/dist/server/config.js.map +1 -1
- package/dist/server/health.d.ts.map +1 -1
- package/dist/server/health.js +8 -0
- package/dist/server/health.js.map +1 -1
- package/dist/server/index.d.ts +16 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +10 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/json-parse-error.d.ts +7 -0
- package/dist/server/json-parse-error.d.ts.map +1 -0
- package/dist/server/json-parse-error.js +28 -0
- package/dist/server/json-parse-error.js.map +1 -0
- package/dist/server/mocks.d.ts +26 -0
- package/dist/server/mocks.d.ts.map +1 -1
- package/dist/server/mocks.js +110 -13
- package/dist/server/mocks.js.map +1 -1
- package/dist/server/plugins.d.ts.map +1 -1
- package/dist/server/plugins.js +56 -11
- package/dist/server/plugins.js.map +1 -1
- package/dist/server/rule-files.d.ts +36 -0
- package/dist/server/rule-files.d.ts.map +1 -1
- package/dist/server/rule-files.js +388 -107
- package/dist/server/rule-files.js.map +1 -1
- package/dist/server/ruleset-lock.d.ts +62 -0
- package/dist/server/ruleset-lock.d.ts.map +1 -0
- package/dist/server/ruleset-lock.js +234 -0
- package/dist/server/ruleset-lock.js.map +1 -0
- package/index.js +128 -16
- package/mcp-server.js +108 -93
- package/package.json +1 -1
- package/plugins/builtin/mock-plugin.ts +9 -5
- package/web/dist/assets/alert-B514Frte.js +1 -0
- package/web/dist/assets/arrow-right-BNum_060.js +1 -0
- package/web/dist/assets/{body-diff-view-B-4XWmL6.js → body-diff-view-SfLCLzs9.js} +2 -2
- package/web/dist/assets/button-BFNlGZ5p.js +1 -0
- package/web/dist/assets/check-BkfHQ6re.js +1 -0
- package/web/dist/assets/checkbox-CQyFsZzS.js +1 -0
- package/web/dist/assets/chevron-down-DHwUtL4O.js +1 -0
- package/web/dist/assets/{circle-alert-Q8cZk9Rv.js → circle-alert-zMagrFuI.js} +1 -1
- package/web/dist/assets/{circle-x-C9USji_R.js → circle-x-BEkinhez.js} +1 -1
- package/web/dist/assets/code-DD-AYxxu.js +1 -0
- package/web/dist/assets/{copy-BYjq1hdN.js → copy-BRnscYy1.js} +1 -1
- package/web/dist/assets/detail-panel-DZRKJG2W.js +4 -0
- package/web/dist/assets/dist-BQN0rzUS.js +1 -0
- package/web/dist/assets/{dist-CEd_wnRC.js → dist-C-E2R2jk.js} +1 -1
- package/web/dist/assets/{dist-C6eaYCUo.js → dist-CFZ4xMSF.js} +1 -1
- package/web/dist/assets/{dist-DWmDaXVN.js → dist-CRmX_NvH.js} +1 -1
- package/web/dist/assets/{dist-C8RHq5At.js → dist-DLQKRjuW.js} +1 -1
- package/web/dist/assets/{dist-Blyulj4_.js → dist-LiAcI8CK.js} +1 -1
- package/web/dist/assets/{dist-Ce4ujolc.js → dist-fzPL-8F9.js} +1 -1
- package/web/dist/assets/{dist-DoE7uybN.js → dist-wv9ebJK_.js} +1 -1
- package/web/dist/assets/editor-pane-actions-_ckwcCII.js +1 -0
- package/web/dist/assets/{es2015-DG46NGN7.js → es2015-DPJTBYa9.js} +1 -1
- package/web/dist/assets/global-panel-shell-Iir-c_pQ.js +6 -0
- package/web/dist/assets/health-panel-Dmf-EhJz.js +1 -0
- package/web/dist/assets/index-C4dIj1nT.js +21 -0
- package/web/dist/assets/index-CdYMl1M7.css +2 -0
- package/web/dist/assets/{mobile-proxy-panel-tU0NyJ3P.js → mobile-proxy-panel-CFcwa11A.js} +6 -6
- package/web/dist/assets/mock-config-CGl50MIr.js +1 -0
- package/web/dist/assets/mock-editor-panel-xcCk23oH.js +20 -0
- package/web/dist/assets/mocks-file-missing-note-CW_ONVLA.js +1 -0
- package/web/dist/assets/monaco-editor-BkP7envQ.js +11 -0
- package/web/dist/assets/pane-bar-5j9Nf7W_.js +1 -0
- package/web/dist/assets/pane-split-C0OSe365.js +1 -0
- package/web/dist/assets/panel-heading-izT5q0ZN.js +1 -0
- package/web/dist/assets/{pencil-line-Cqe41E4h.js → pencil-line-CEZ7D-de.js} +1 -1
- package/web/dist/assets/plugin-code-editor-DKE9dFEE.js +2 -0
- package/web/dist/assets/plugin-config-Cg09TsuV.js +2 -0
- package/web/dist/assets/plugin-generator-CT1f1tu0.js +2 -0
- package/web/dist/assets/plugin-test-dialog-BbLIMnkJ.js +7 -0
- package/web/dist/assets/plus-ixZQRprm.js +1 -0
- package/web/dist/assets/{refresh-cw-CG3HaiQT.js → refresh-cw-BfLoHqg5.js} +1 -1
- package/web/dist/assets/rotate-ccw-CRguJya4.js +1 -0
- package/web/dist/assets/route-preview-CKO7nF4W.js +1 -0
- package/web/dist/assets/{rule-ai-assistant-panel-DMh0F9dr.js → rule-ai-assistant-panel-CtfUqdYG.js} +2 -2
- package/web/dist/assets/rule-config-Cb4ejIXI.js +15 -0
- package/web/dist/assets/rule-overview-panel-xtHTS27O.js +1 -0
- package/web/dist/assets/{server-Cpj4KNwN.js → server-eYXq3PbE.js} +1 -1
- package/web/dist/assets/settings-panel-CpQuE6nr.js +1 -0
- package/web/dist/assets/{shield-check-OqUmVLQt.js → shield-check-hQXjdfZ7.js} +1 -1
- package/web/dist/assets/smartphone-D4Q7cQfm.js +1 -0
- package/web/dist/assets/switch-94vgsMHm.js +1 -0
- package/web/dist/assets/{syntax-highlight-Dk0-ewLI.js → syntax-highlight-BFgN_C0Y.js} +1 -1
- package/web/dist/assets/{table-2-BR5e3z8m.js → table-2-DvGHhMm4.js} +1 -1
- package/web/dist/assets/table-DaXD6jJv.js +1 -0
- package/web/dist/assets/textarea-9wB8Hsz-.js +1 -0
- package/web/dist/assets/toast-9yeTf7d9.js +1 -0
- package/web/dist/assets/use-mocks-file-status-BUIUJ4oQ.js +1 -0
- package/web/dist/assets/{wand-sparkles-CJ7fSnom.js → wand-sparkles-Bmt4dDOf.js} +1 -1
- package/web/dist/assets/{wifi-CCLKyySQ.js → wifi-Cjj_Br1u.js} +1 -1
- package/web/dist/assets/{zap-DR-xiUMe.js → zap-BuOtogAv.js} +1 -1
- package/web/dist/index.html +16 -10
- package/web/dist/assets/alert-DCvlZAiv.js +0 -1
- package/web/dist/assets/arrow-right-B4Mrlj2-.js +0 -1
- package/web/dist/assets/button-BTToMaBW.js +0 -1
- package/web/dist/assets/checkbox-D1Z9DtjF.js +0 -1
- package/web/dist/assets/chevron-down-CPngAx4M.js +0 -1
- package/web/dist/assets/chevron-right-V1DGIDbL.js +0 -1
- package/web/dist/assets/code-5_eJ_XpJ.js +0 -1
- package/web/dist/assets/code-fixer-D48sCDtp.js +0 -20
- package/web/dist/assets/detail-panel-BSFz9CUw.js +0 -4
- package/web/dist/assets/file-system-access-_og-Tg9o.js +0 -1
- package/web/dist/assets/global-panel-shell-5cJneeis.js +0 -6
- package/web/dist/assets/health-panel-BS3L2e5d.js +0 -1
- package/web/dist/assets/index-CvTR1IVi.js +0 -18
- package/web/dist/assets/index-bfV7sxx5.css +0 -2
- package/web/dist/assets/mock-config-CULJP6Vt.js +0 -1
- package/web/dist/assets/mock-editor-panel-9wlDaySA.js +0 -1
- package/web/dist/assets/monaco-editor-BttaUoOz.js +0 -11
- package/web/dist/assets/plugin-code-editor-BAIBp4lg.js +0 -2
- package/web/dist/assets/plugin-config-ButUV2SZ.js +0 -1
- package/web/dist/assets/plugin-generator-uceUPSCv.js +0 -2
- package/web/dist/assets/plugin-test-dialog-BrJ4HfRX.js +0 -7
- package/web/dist/assets/plus-tVxSydJ8.js +0 -1
- package/web/dist/assets/rotate-ccw-CAYM6Vv0.js +0 -1
- package/web/dist/assets/route-preview-C4cGrS1-.js +0 -1
- package/web/dist/assets/rule-config-BTTGNwMs.js +0 -15
- package/web/dist/assets/rule-overview-panel-SFR2OloL.js +0 -1
- package/web/dist/assets/select-aBXQ0zNa.js +0 -1
- package/web/dist/assets/settings-panel-BMReDG8b.js +0 -1
- package/web/dist/assets/smartphone-DKeDuJTU.js +0 -1
- package/web/dist/assets/switch-Bes_YiJu.js +0 -1
- package/web/dist/assets/table-nW7uQVcl.js +0 -1
- package/web/dist/assets/textarea-BSI0rnW6.js +0 -1
- /package/web/dist/assets/{babel-DCr4VA1c.js → babel-bwZm0qi3.js} +0 -0
- /package/web/dist/assets/{clipboard-VhPXNN5m.js → clipboard-Bt1qY01V.js} +0 -0
- /package/web/dist/assets/{estree-qO_m2Qaw.js → estree-MatbZDwq.js} +0 -0
- /package/web/dist/assets/{html-VDGahtyk.js → html-CCrsbFYX.js} +0 -0
- /package/web/dist/assets/{postcss-BUm8EKMw.js → postcss-BxN7Gr8n.js} +0 -0
- /package/web/dist/assets/{standalone-Bj8SSd8Q.js → standalone-DsnFnsqM.js} +0 -0
package/index.js
CHANGED
|
@@ -120,8 +120,39 @@ const { createPluginBootstrapRunner } = require('./dist/core/plugin-bootstrap-ru
|
|
|
120
120
|
const { openBrowserWithProxy } = require('./dist/core/browser')
|
|
121
121
|
const { handleLocalRequest } = require('./dist/core/static-server')
|
|
122
122
|
const { createConfigDiagnostics } = require('./dist/core/config-diagnostics')
|
|
123
|
-
const { appendProxyRecord } = require('./dist/core/proxy-record')
|
|
123
|
+
const { appendProxyRecord, appendUpstreamErrorRecord } = require('./dist/core/proxy-record')
|
|
124
124
|
const { createRuntimeHealthMonitor, parseWatchdogConfig } = require('./dist/core/runtime-health')
|
|
125
|
+
|
|
126
|
+
function previewRequestBody(reqBody) {
|
|
127
|
+
if (!reqBody) return ''
|
|
128
|
+
try {
|
|
129
|
+
if (Buffer.isBuffer(reqBody)) {
|
|
130
|
+
const slice = reqBody.length > 65536 ? reqBody.subarray(0, 65536) : reqBody
|
|
131
|
+
return slice.toString('utf8')
|
|
132
|
+
}
|
|
133
|
+
return String(reqBody)
|
|
134
|
+
} catch (_) {
|
|
135
|
+
return ''
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Send 502 and put the failed request in the traffic list (issue #160). */
|
|
140
|
+
function failProxyWithUpstreamError(res, info) {
|
|
141
|
+
finishResponseWithProxyError(res, info.statusCode || 502)
|
|
142
|
+
try {
|
|
143
|
+
appendUpstreamErrorRecord(ctx, {
|
|
144
|
+
method: info.method,
|
|
145
|
+
source: info.source,
|
|
146
|
+
target: info.target,
|
|
147
|
+
startTime: info.startTime || Date.now(),
|
|
148
|
+
clientIdentity: info.clientIdentity,
|
|
149
|
+
requestHeaders: info.requestHeaders,
|
|
150
|
+
requestBody: previewRequestBody(info.reqBody),
|
|
151
|
+
err: info.err,
|
|
152
|
+
statusCode: info.statusCode || 502,
|
|
153
|
+
})
|
|
154
|
+
} catch (_) {}
|
|
155
|
+
}
|
|
125
156
|
const { createRateLimitedLogger } = require('./dist/core/log-rate-limit')
|
|
126
157
|
|
|
127
158
|
const remoteAccess = buildRemoteAccessConfig()
|
|
@@ -200,7 +231,10 @@ const { createApp } = require('./dist/server/index')
|
|
|
200
231
|
let configDiag = null
|
|
201
232
|
|
|
202
233
|
const serverContext = {
|
|
203
|
-
|
|
234
|
+
// Issue #118: the mocks path must also live only on ctx. refresh-config
|
|
235
|
+
// reset a stale serverContext copy, so the proxy kept the old file.
|
|
236
|
+
get currentMocksPath() { return ctx.currentMocksPath },
|
|
237
|
+
set currentMocksPath(p) { ctx.currentMocksPath = p },
|
|
204
238
|
routeRules: ctx.routeRules,
|
|
205
239
|
ruleMap: ctx.ruleMap,
|
|
206
240
|
excludeMap: ctx.excludeMap,
|
|
@@ -208,8 +242,14 @@ const serverContext = {
|
|
|
208
242
|
proxyRecordArr: ctx.proxyRecordArr,
|
|
209
243
|
proxyRecordDetailMap: ctx.proxyRecordDetailMap,
|
|
210
244
|
recordIdSeq: ctx.recordIdSeq,
|
|
211
|
-
|
|
212
|
-
|
|
245
|
+
// Issue #115: mock state must live in exactly one place. Plain copies went
|
|
246
|
+
// stale because /api handlers run asynchronously (after body parsing),
|
|
247
|
+
// after handleLocalRequest had already synced serverContext -> ctx, so
|
|
248
|
+
// every quick create reused the same id and overwrote the previous rule.
|
|
249
|
+
get mockRules() { return ctx.mockRules },
|
|
250
|
+
set mockRules(rules) { ctx.mockRules = rules },
|
|
251
|
+
get mockIdSeq() { return ctx.mockIdSeq },
|
|
252
|
+
set mockIdSeq(seq) { ctx.mockIdSeq = seq },
|
|
213
253
|
requestPipeline: ctx.requestPipeline,
|
|
214
254
|
builtinLoggerPlugin: ctx.builtinLoggerPlugin,
|
|
215
255
|
shadowCompareTracker: ctx.shadowCompareTracker,
|
|
@@ -218,6 +258,7 @@ const serverContext = {
|
|
|
218
258
|
hookDispatcher: ctx.hookDispatcher,
|
|
219
259
|
settingsPath: ctx.settingsPath,
|
|
220
260
|
meddleDir: ctx.meddleDir,
|
|
261
|
+
appInfo: { name: require('./package.json').name, version: require('./package.json').version },
|
|
221
262
|
settings: null,
|
|
222
263
|
loadMockRules: () => {
|
|
223
264
|
mockHandler.loadMockRules()
|
|
@@ -225,7 +266,10 @@ const serverContext = {
|
|
|
225
266
|
serverContext.mockIdSeq = ctx.mockIdSeq
|
|
226
267
|
},
|
|
227
268
|
saveMockRules: () => mockHandler.saveMockRules(),
|
|
269
|
+
assertMocksWritable: () => mockHandler.assertMocksWritable(),
|
|
270
|
+
getMocksParseError: () => mockHandler.getMocksParseError(),
|
|
228
271
|
reloadCustomPlugins: () => pluginBoot.reloadCustomPlugins(),
|
|
272
|
+
unloadCustomPlugin: (pluginId) => pluginBoot.unloadCustomPlugin(pluginId),
|
|
229
273
|
logRuleMap: () => routeLoader.logRuleMap(),
|
|
230
274
|
reloadAllRuleFiles: () => routeLoader.reloadAllRuleFiles(),
|
|
231
275
|
broadcastToAllClients: (data) => {
|
|
@@ -264,7 +308,20 @@ const expressApp = createApp(serverContext)
|
|
|
264
308
|
|
|
265
309
|
// ===== 加载 Mock 和路由规则 =====
|
|
266
310
|
serverContext.loadMockRules()
|
|
311
|
+
{
|
|
312
|
+
// Issue #121: relative mocksFilePath now resolves against MEDDLE_HOME; warn once
|
|
313
|
+
// if the file only exists under the old rule (start directory). No auto-migration.
|
|
314
|
+
const { detectLegacyMocksPath, readConfiguredMocksPath, formatLegacyMocksNotice } = require('./dist/core/mock-handler')
|
|
315
|
+
const legacy = detectLegacyMocksPath(readConfiguredMocksPath(ctx.settingsPath), ctx.meddleDir)
|
|
316
|
+
if (legacy) console.warn(formatLegacyMocksNotice(legacy))
|
|
317
|
+
}
|
|
267
318
|
routeLoader.initRouteRules()
|
|
319
|
+
// Hot-reload mocks when the file is edited externally (CLI file-mode / hand edit). Issue #85.
|
|
320
|
+
mockHandler.initMockFileWatcher(() => {
|
|
321
|
+
serverContext.mockRules = ctx.mockRules
|
|
322
|
+
serverContext.mockIdSeq = ctx.mockIdSeq
|
|
323
|
+
serverContext.broadcastToAllClients({ type: 'mocksUpdated', rules: ctx.mockRules })
|
|
324
|
+
})
|
|
268
325
|
|
|
269
326
|
// ===== Cross-Origin 插件 =====
|
|
270
327
|
const plugins = [{
|
|
@@ -400,6 +457,24 @@ const proxyServer = http.createServer(async (req, res) => {
|
|
|
400
457
|
const url = new URL(target.startsWith('http') ? target : req.url, 'http://' + req.headers.host)
|
|
401
458
|
const routeChanged = source !== url.href
|
|
402
459
|
const startTime = Date.now()
|
|
460
|
+
let upstreamFailureRecorded = false
|
|
461
|
+
const failUpstream = (err) => {
|
|
462
|
+
if (upstreamFailureRecorded) {
|
|
463
|
+
finishResponseWithProxyError(res)
|
|
464
|
+
return
|
|
465
|
+
}
|
|
466
|
+
upstreamFailureRecorded = true
|
|
467
|
+
failProxyWithUpstreamError(res, {
|
|
468
|
+
method: req.method,
|
|
469
|
+
source,
|
|
470
|
+
target: url.href,
|
|
471
|
+
startTime,
|
|
472
|
+
clientIdentity,
|
|
473
|
+
requestHeaders: req.headers,
|
|
474
|
+
reqBody,
|
|
475
|
+
err,
|
|
476
|
+
})
|
|
477
|
+
}
|
|
403
478
|
const proxyReq = http.request(url, { method: req.method, headers: stripProxyHeaders(req.headers) }, (proxyRes) => {
|
|
404
479
|
const resChunks = []
|
|
405
480
|
const pluginResponseInterception = pluginIntercept.shouldInterceptResponse()
|
|
@@ -411,7 +486,7 @@ const proxyServer = http.createServer(async (req, res) => {
|
|
|
411
486
|
proxyResponseSettled = true
|
|
412
487
|
pluginIntercept.emitLegacyErrorToPlugins('onBeforeResponse', err)
|
|
413
488
|
logRuntimeError(`http:proxy-response:${getErrorKey(err)}`, 'HTTP proxy response error:', getErrorMessage(err))
|
|
414
|
-
|
|
489
|
+
failUpstream(err)
|
|
415
490
|
}
|
|
416
491
|
if (routeChanged) proxyRes.headers['x-real-url'] = url.href
|
|
417
492
|
if (!intercepting) res.writeHead(proxyRes.statusCode, proxyRes.headers)
|
|
@@ -474,7 +549,7 @@ const proxyServer = http.createServer(async (req, res) => {
|
|
|
474
549
|
} catch (err) {
|
|
475
550
|
pluginIntercept.emitLegacyErrorToPlugins('onBeforeResponse', err)
|
|
476
551
|
logRuntimeError(`http:response-handler:${getErrorKey(err)}`, 'HTTP proxy response handling error:', getErrorMessage(err))
|
|
477
|
-
|
|
552
|
+
failUpstream(err)
|
|
478
553
|
}
|
|
479
554
|
})
|
|
480
555
|
})
|
|
@@ -490,14 +565,22 @@ const proxyServer = http.createServer(async (req, res) => {
|
|
|
490
565
|
} else {
|
|
491
566
|
logRuntimeError(`http:proxy-request:${getErrorKey(err)}`, 'HTTP proxy error:', getErrorMessage(err))
|
|
492
567
|
}
|
|
493
|
-
|
|
568
|
+
failUpstream(err)
|
|
494
569
|
})
|
|
495
570
|
proxyReq.write(reqBody)
|
|
496
571
|
proxyReq.end()
|
|
497
572
|
} catch (err) {
|
|
498
573
|
pluginIntercept.emitLegacyErrorToPlugins('onBeforeResponse', err)
|
|
499
574
|
logRuntimeError(`http:request-handler:${getErrorKey(err)}`, 'HTTP proxy request failed:', getErrorMessage(err))
|
|
500
|
-
|
|
575
|
+
failProxyWithUpstreamError(res, {
|
|
576
|
+
method: req.method,
|
|
577
|
+
source,
|
|
578
|
+
target: source,
|
|
579
|
+
startTime: Date.now(),
|
|
580
|
+
clientIdentity,
|
|
581
|
+
requestHeaders: req.headers,
|
|
582
|
+
err,
|
|
583
|
+
})
|
|
501
584
|
}
|
|
502
585
|
})
|
|
503
586
|
})
|
|
@@ -523,7 +606,7 @@ localWSServer.on('connection', (client, req) => {})
|
|
|
523
606
|
await ensureRootCA({ trustCheckAsync: true })
|
|
524
607
|
await pluginBoot.bootstrapBuiltinPlugins()
|
|
525
608
|
const port = await getFreePort()
|
|
526
|
-
proxyServer.listen(port, remoteAccess.bindHost, () => {
|
|
609
|
+
proxyServer.listen(port, remoteAccess.bindHost, async () => {
|
|
527
610
|
const proxyUrl = `http://127.0.0.1:${port}`
|
|
528
611
|
console.log(chalk.green('代理服务器已启动:'), chalk.cyan(proxyUrl))
|
|
529
612
|
proxyDebug('proxy-server start on ' + chalk.green(proxyUrl))
|
|
@@ -531,16 +614,19 @@ localWSServer.on('connection', (client, req) => {})
|
|
|
531
614
|
if (ctx.requestPipeline.mode === 'on') {
|
|
532
615
|
proxyDebug('plugin on host allowlist: ' + (ctx.PLUGIN_ON_HOSTS.size > 0 ? Array.from(ctx.PLUGIN_ON_HOSTS).join(',') : '(all)'))
|
|
533
616
|
}
|
|
534
|
-
|
|
617
|
+
// Always write probe file so CLI can find this instance (issue #85).
|
|
618
|
+
// Previously only wrote under MEDDLE_MCP, leaving stale ports after normal starts.
|
|
619
|
+
{
|
|
535
620
|
const mcpFile = path.join(ctx.meddleDir, 'mcp-proxy-url.json')
|
|
536
|
-
const mcpData = { proxyUrl }
|
|
621
|
+
const mcpData = { proxyUrl, pid: process.pid, startedAt: new Date().toISOString() }
|
|
537
622
|
if (process.env.MEDDLE_OPEN_CHROMEDEVTOOLS) mcpData.remoteDebuggingPort = 9222
|
|
538
623
|
fs.writeFileSync(mcpFile, JSON.stringify(mcpData), 'utf8')
|
|
539
624
|
}
|
|
540
625
|
if (ctx.AUTO_OPEN) {
|
|
541
626
|
const proxyAddr = `127.0.0.1:${port}`
|
|
542
627
|
const remotePort = process.env.MEDDLE_OPEN_CHROMEDEVTOOLS ? 9222 : undefined
|
|
543
|
-
|
|
628
|
+
const opened = await openBrowserWithProxy(proxyUrl, proxyAddr, ctx.meddleDir, remotePort).catch(() => false)
|
|
629
|
+
if (opened) {
|
|
544
630
|
console.log(chalk.green('已启动浏览器(代理:'), proxyAddr + chalk.green(')'))
|
|
545
631
|
} else {
|
|
546
632
|
console.log(chalk.yellow('浏览器并未启动,请手动打开'), chalk.cyan(proxyUrl), chalk.yellow('并设置代理'), proxyAddr)
|
|
@@ -661,6 +747,24 @@ proxyServer.on('connect', async (req, socket, head) => {
|
|
|
661
747
|
|
|
662
748
|
const routeChanged = source !== target
|
|
663
749
|
const startTime = Date.now()
|
|
750
|
+
let upstreamFailureRecorded = false
|
|
751
|
+
const failUpstream = (err) => {
|
|
752
|
+
if (upstreamFailureRecorded) {
|
|
753
|
+
finishResponseWithProxyError(res)
|
|
754
|
+
return
|
|
755
|
+
}
|
|
756
|
+
upstreamFailureRecorded = true
|
|
757
|
+
failProxyWithUpstreamError(res, {
|
|
758
|
+
method: req.method,
|
|
759
|
+
source,
|
|
760
|
+
target,
|
|
761
|
+
startTime,
|
|
762
|
+
clientIdentity: requestClientIdentity,
|
|
763
|
+
requestHeaders: req.headers,
|
|
764
|
+
reqBody,
|
|
765
|
+
err,
|
|
766
|
+
})
|
|
767
|
+
}
|
|
664
768
|
try {
|
|
665
769
|
const proxyRes = await makeProxyRequest(target, req.method, stripProxyHeaders(req.headers), reqBody)
|
|
666
770
|
const resChunks = []
|
|
@@ -673,7 +777,7 @@ proxyServer.on('connect', async (req, socket, head) => {
|
|
|
673
777
|
proxyResponseSettled = true
|
|
674
778
|
pluginIntercept.emitLegacyErrorToPlugins('onBeforeResponse', err)
|
|
675
779
|
logRuntimeError(`https:upstream-stream:${originHost}:${getErrorKey(err)}`, '[proxy] upstream stream %s: %s', originHost + req.url, getErrorMessage(err))
|
|
676
|
-
|
|
780
|
+
failUpstream(err)
|
|
677
781
|
}
|
|
678
782
|
if (routeChanged) proxyRes.headers['x-real-url'] = target
|
|
679
783
|
if (!intercepting) res.writeHead(proxyRes.statusCode, cleanHeadersForH2(proxyRes.headers))
|
|
@@ -737,7 +841,7 @@ proxyServer.on('connect', async (req, socket, head) => {
|
|
|
737
841
|
} catch (err) {
|
|
738
842
|
pluginIntercept.emitLegacyErrorToPlugins('onBeforeResponse', err)
|
|
739
843
|
logRuntimeError(`https:response-handler:${originHost}:${getErrorKey(err)}`, '[proxy] upstream response handling %s: %s', originHost + req.url, getErrorMessage(err))
|
|
740
|
-
|
|
844
|
+
failUpstream(err)
|
|
741
845
|
}
|
|
742
846
|
})
|
|
743
847
|
} catch (err) {
|
|
@@ -751,12 +855,20 @@ proxyServer.on('connect', async (req, socket, head) => {
|
|
|
751
855
|
logRuntimeError(`https:upstream:${originHost}:${getErrorKey(err)}`, '[proxy] upstream %s: %s', originHost + req.url, getErrorMessage(err))
|
|
752
856
|
}
|
|
753
857
|
pluginIntercept.emitLegacyErrorToPlugins('onBeforeResponse', err)
|
|
754
|
-
|
|
858
|
+
failUpstream(err)
|
|
755
859
|
}
|
|
756
860
|
} catch (err) {
|
|
757
861
|
pluginIntercept.emitLegacyErrorToPlugins('onBeforeResponse', err)
|
|
758
862
|
logRuntimeError(`https:request-handler:${originHost}:${getErrorKey(err)}`, '[proxy] HTTPS request handling %s: %s', originHost + req.url, getErrorMessage(err))
|
|
759
|
-
|
|
863
|
+
failProxyWithUpstreamError(res, {
|
|
864
|
+
method: req.method,
|
|
865
|
+
source,
|
|
866
|
+
target: source,
|
|
867
|
+
startTime: Date.now(),
|
|
868
|
+
clientIdentity: requestClientIdentity,
|
|
869
|
+
requestHeaders: req.headers,
|
|
870
|
+
err,
|
|
871
|
+
})
|
|
760
872
|
}
|
|
761
873
|
})
|
|
762
874
|
})
|