@jonathanleelx/meddle 0.4.1-beta.2 → 0.4.2

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.
@@ -0,0 +1,192 @@
1
+ /**
2
+ * meddle crash-report — crash dump collection with fingerprinting and dedupe.
3
+ *
4
+ * Pure JS so it runs unmodified in the npm package and in the deno-compiled
5
+ * binary (bin/ is bundled at compile time). Never throws: the crash reporter
6
+ * must not crash while reporting a crash.
7
+ *
8
+ * Layout: <home>/crash/<fingerprint>-<timestamp>.json
9
+ *
10
+ * Privacy: only env KEY names are recorded, never their values; request
11
+ * headers/URLs are scrubbed of common credential patterns before writing.
12
+ */
13
+
14
+ const fs = require('fs')
15
+ const os = require('os')
16
+ const path = require('path')
17
+
18
+ const CRASH_DIR = 'crash'
19
+ const MAX_FILES_PER_FINGERPRINT = 10
20
+
21
+ const SENSITIVE_HEADER_PATTERNS = [
22
+ /authorization/i,
23
+ /cookie/i,
24
+ /set-cookie/i,
25
+ /token/i,
26
+ /session/i,
27
+ /x-api-key/i,
28
+ ]
29
+
30
+ const SENSITIVE_QUERY_PATTERNS = [
31
+ /token=/i,
32
+ /key=/i,
33
+ /secret=/i,
34
+ /password=/i,
35
+ /apikey=/i,
36
+ /appid=/i,
37
+ /app_id=/i,
38
+ ]
39
+
40
+ /**
41
+ * Normalize a stack frame into a stable token:
42
+ * - file:///.../dist/core/h2-pool.js:182:30 → h2-pool.js
43
+ * - ext:deno_node/net.ts:1:21858 → net.ts
44
+ * - node:internal/timers.mjs:4:1174 → internal/timers.mjs
45
+ * - (file:///x/dist/a.js:10:20) → a.js
46
+ * Line/column numbers and volatile absolute paths are stripped so identical
47
+ * crashes (different addresses/timestamps) share one fingerprint.
48
+ */
49
+ function normalizeFrame(frame) {
50
+ const line = frame.trim()
51
+ const match = line.match(/\(?(?:file:\/\/)?[^)\s]*[\\/]([^\\/)\s:]+\.(?:js|ts|mjs|cjs))(?::\d+:\d+)?\)?/)
52
+ if (match) return match[1]
53
+ // bare frames like "at f (a.js:10:20)" or "at TCPWrap.onStreamRead"
54
+ const bare = line.match(/at\s+([\w.]+)(?:\s*\(.*\))?$/)
55
+ if (bare) return bare[1]
56
+ return line.slice(0, 80)
57
+ }
58
+
59
+ /**
60
+ * Build a stable fingerprint for an error. Prefers the error `code` when
61
+ * present (e.g. ECONNRESET); otherwise the first stack frame chain is
62
+ * normalized to remove addresses, paths and line numbers.
63
+ * @param {Error & { code?: string }} err
64
+ * @returns {string}
65
+ */
66
+ function crashFingerprint(err) {
67
+ if (err && typeof err.code === 'string' && err.code) return err.code
68
+ if (err && typeof err.stack === 'string') {
69
+ const lines = err.stack.split('\n').slice(0, 5)
70
+ const frames = lines.map(normalizeFrame).filter(Boolean)
71
+ if (frames.length > 0) return frames.join('|').slice(0, 120)
72
+ }
73
+ if (err && err.message) return String(err.message).slice(0, 120)
74
+ return 'unknown'
75
+ }
76
+
77
+ /**
78
+ * Strip credential-shaped values from a free-form object (headers, URL query).
79
+ * Returns a new object with sensitive keys replaced by a marker.
80
+ */
81
+ function scrubSensitive(input) {
82
+ if (!input || typeof input !== 'object') return input
83
+ const out = {}
84
+ for (const [key, value] of Object.entries(input)) {
85
+ const k = String(key)
86
+ const isSensitiveHeader = SENSITIVE_HEADER_PATTERNS.some(p => p.test(k))
87
+ const isSensitiveQuery = SENSITIVE_QUERY_PATTERNS.some(p => p.test(k))
88
+ if (isSensitiveHeader || isSensitiveQuery) {
89
+ out[k] = '[redacted]'
90
+ } else {
91
+ out[k] = value
92
+ }
93
+ }
94
+ return out
95
+ }
96
+
97
+ /**
98
+ * Sanitize crash context before it is written to disk.
99
+ * Only env key names are kept (never values); request metadata is scrubbed.
100
+ * @param {{ envKeys?: string[], requestUrl?: string, headers?: Record<string, string> }} input
101
+ * @returns {Record<string, unknown>}
102
+ */
103
+ function sanitizeCrashData(input) {
104
+ const out = {}
105
+ if (Array.isArray(input.envKeys)) {
106
+ out.envKeys = input.envKeys.filter(k => typeof k === 'string')
107
+ }
108
+ if (typeof input.requestUrl === 'string') {
109
+ try {
110
+ const u = new URL(input.requestUrl)
111
+ out.requestUrl = u.origin + u.pathname
112
+ if (u.search) {
113
+ const params = new URLSearchParams(u.search)
114
+ const scrubbed = {}
115
+ for (const [k, v] of params) {
116
+ scrubbed[k] = SENSITIVE_QUERY_PATTERNS.some(p => p.test(k + '=')) ? '[redacted]' : v
117
+ }
118
+ out.requestQuery = scrubbed
119
+ }
120
+ } catch (_) {
121
+ out.requestUrl = '[invalid-url]'
122
+ }
123
+ }
124
+ if (input.headers && typeof input.headers === 'object') {
125
+ out.headers = scrubSensitive(input.headers)
126
+ }
127
+ return out
128
+ }
129
+
130
+ /**
131
+ * Record a crash dump to <home>/crash/<fingerprint>-<ts>.json.
132
+ * Dedupes by fingerprint (max MAX_FILES_PER_FINGERPRINT). Never throws.
133
+ * @param {{ home: string, error: Error, version?: string, platform?: string,
134
+ * envKeys?: string[], requestUrl?: string, headers?: Record<string, string>,
135
+ * context?: Record<string, unknown>, now?: number }} opts
136
+ * @returns {string} the written file path (or '' when the write failed)
137
+ */
138
+ function recordCrash(opts) {
139
+ try {
140
+ const home = opts.home
141
+ const error = opts.error || new Error('unknown')
142
+ const now = opts.now !== undefined ? opts.now : Date.now()
143
+ const fingerprint = crashFingerprint(error)
144
+ const crashDir = path.join(home, CRASH_DIR)
145
+ fs.mkdirSync(crashDir, { recursive: true })
146
+
147
+ // Dedupe: evict oldest files of the same fingerprint beyond the cap.
148
+ const existing = fs.readdirSync(crashDir)
149
+ .filter(f => f.startsWith(fingerprint + '-'))
150
+ .sort()
151
+ while (existing.length >= MAX_FILES_PER_FINGERPRINT) {
152
+ const oldest = existing.shift()
153
+ if (oldest) {
154
+ try { fs.unlinkSync(path.join(crashDir, oldest)) } catch (_) {}
155
+ }
156
+ }
157
+
158
+ const sanitized = sanitizeCrashData({
159
+ envKeys: opts.envKeys,
160
+ requestUrl: opts.requestUrl,
161
+ headers: opts.headers,
162
+ })
163
+
164
+ const data = {
165
+ fingerprint,
166
+ timestamp: now,
167
+ version: opts.version || '',
168
+ platform: opts.platform || `${os.platform()}-${os.arch()}`,
169
+ error: {
170
+ name: error.name || 'Error',
171
+ message: error.message || String(error),
172
+ stack: typeof error.stack === 'string' ? error.stack : String(error),
173
+ },
174
+ ...sanitized,
175
+ context: opts.context || {},
176
+ }
177
+
178
+ const file = path.join(crashDir, `${fingerprint}-${now}.json`)
179
+ fs.writeFileSync(file, JSON.stringify(data, null, 2), 'utf8')
180
+ return file
181
+ } catch (_) {
182
+ return ''
183
+ }
184
+ }
185
+
186
+ module.exports = {
187
+ crashFingerprint,
188
+ sanitizeCrashData,
189
+ recordCrash,
190
+ CRASH_DIR,
191
+ MAX_FILES_PER_FINGERPRINT,
192
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"connect-tunnel.d.ts","sourceRoot":"","sources":["../../core/connect-tunnel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,KAAK,CAAA;AAEjC,eAAO,MAAM,4BAA4B,uEAC+B,CAAA;AAExE,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAI7D;AAED,wBAAgB,sBAAsB,CAClC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,MAAqC,GAChD,IAAI,CAgCN"}
1
+ {"version":3,"file":"connect-tunnel.d.ts","sourceRoot":"","sources":["../../core/connect-tunnel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,KAAK,CAAA;AAEjC,eAAO,MAAM,4BAA4B,uEAC+B,CAAA;AAExE,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAI7D;AAED,wBAAgB,sBAAsB,CAClC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,MAAqC,GAChD,IAAI,CAsCN"}
@@ -34,8 +34,14 @@ function establishConnectTunnel(client, upstream, head, response = exports.CONNE
34
34
  }
35
35
  client.once('close', closeBoth);
36
36
  upstream.once('close', closeBoth);
37
- client.once('error', closeBoth);
38
- upstream.once('error', closeBoth);
37
+ // Persistent error listeners: a tunnel socket can emit 'error' more
38
+ // than once (e.g. an RST while the other side is still draining). With
39
+ // `once` the listener would be consumed by the first error and a second
40
+ // emission would crash the process via uncaughtException (observed in
41
+ // the deno binary). `on` keeps the bridge protected for the socket's
42
+ // lifetime; closeBoth is idempotent.
43
+ client.on('error', closeBoth);
44
+ upstream.on('error', closeBoth);
39
45
  if (head.length > 0)
40
46
  upstream.write(head);
41
47
  client.pipe(upstream);
@@ -1 +1 @@
1
- {"version":3,"file":"connect-tunnel.js","sourceRoot":"","sources":["../../core/connect-tunnel.ts"],"names":[],"mappings":";;;AAKA,sDAIC;AAED,wDAqCC;AA9CY,QAAA,4BAA4B,GACrC,oEAAoE,CAAA;AAExE,SAAgB,qBAAqB,CAAC,KAAc;IAChD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IAC3E,MAAM,IAAI,GAAI,KAA+B,CAAC,IAAI,CAAA;IAClD,OAAO,IAAI,KAAK,YAAY,IAAI,IAAI,KAAK,OAAO,CAAA;AACpD,CAAC;AAED,SAAgB,sBAAsB,CAClC,MAAc,EACd,QAAgB,EAChB,IAAY,EACZ,WAAmB,oCAA4B;IAE/C,IAAI,MAAM,GAAG,KAAK,CAAA;IAClB,MAAM,SAAS,GAAG,GAAS,EAAE;QACzB,IAAI,MAAM;YAAE,OAAM;QAClB,MAAM,GAAG,IAAI,CAAA;QAEb,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QACvB,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACvB,IAAI,CAAC,MAAM,CAAC,SAAS;YAAE,MAAM,CAAC,OAAO,EAAE,CAAA;QACvC,IAAI,CAAC,QAAQ,CAAC,SAAS;YAAE,QAAQ,CAAC,OAAO,EAAE,CAAA;IAC/C,CAAC,CAAA;IAED,IAAI,MAAM,CAAC,SAAS,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;QACzC,SAAS,EAAE,CAAA;QACX,OAAM;IACV,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,IAAI,MAAM,CAAC,SAAS,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;YACzC,SAAS,EAAE,CAAA;YACX,OAAM;QACV,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;QAC/B,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;QACjC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;QAC/B,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;QAEjC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACzC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACrB,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACzB,CAAC,CAAC,CAAA;AACN,CAAC"}
1
+ {"version":3,"file":"connect-tunnel.js","sourceRoot":"","sources":["../../core/connect-tunnel.ts"],"names":[],"mappings":";;;AAKA,sDAIC;AAED,wDA2CC;AApDY,QAAA,4BAA4B,GACrC,oEAAoE,CAAA;AAExE,SAAgB,qBAAqB,CAAC,KAAc;IAChD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IAC3E,MAAM,IAAI,GAAI,KAA+B,CAAC,IAAI,CAAA;IAClD,OAAO,IAAI,KAAK,YAAY,IAAI,IAAI,KAAK,OAAO,CAAA;AACpD,CAAC;AAED,SAAgB,sBAAsB,CAClC,MAAc,EACd,QAAgB,EAChB,IAAY,EACZ,WAAmB,oCAA4B;IAE/C,IAAI,MAAM,GAAG,KAAK,CAAA;IAClB,MAAM,SAAS,GAAG,GAAS,EAAE;QACzB,IAAI,MAAM;YAAE,OAAM;QAClB,MAAM,GAAG,IAAI,CAAA;QAEb,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QACvB,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACvB,IAAI,CAAC,MAAM,CAAC,SAAS;YAAE,MAAM,CAAC,OAAO,EAAE,CAAA;QACvC,IAAI,CAAC,QAAQ,CAAC,SAAS;YAAE,QAAQ,CAAC,OAAO,EAAE,CAAA;IAC/C,CAAC,CAAA;IAED,IAAI,MAAM,CAAC,SAAS,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;QACzC,SAAS,EAAE,CAAA;QACX,OAAM;IACV,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,IAAI,MAAM,CAAC,SAAS,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;YACzC,SAAS,EAAE,CAAA;YACX,OAAM;QACV,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;QAC/B,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;QACjC,oEAAoE;QACpE,uEAAuE;QACvE,wEAAwE;QACxE,sEAAsE;QACtE,qEAAqE;QACrE,qCAAqC;QACrC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;QAC7B,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;QAE/B,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACzC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACrB,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IACzB,CAAC,CAAC,CAAA;AACN,CAAC"}
@@ -1,4 +1,7 @@
1
+ import * as http2 from 'http2';
1
2
  import type { ProxyResponse } from './types';
3
+ export declare const UPSTREAM_TIMEOUT_CODE = "UPSTREAM_TIMEOUT";
2
4
  export declare function cleanHeadersForH2(headers: Record<string, any>): Record<string, any>;
5
+ export declare function __setH2SessionFactoryForTest(factory: ((origin: string, opts: http2.SecureClientSessionOptions) => http2.ClientHttp2Session) | null): void;
3
6
  export declare function makeProxyRequest(target: string, method: string, headers: Record<string, any>, reqBody: Buffer): Promise<ProxyResponse>;
4
7
  //# sourceMappingURL=h2-pool.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"h2-pool.d.ts","sourceRoot":"","sources":["../../core/h2-pool.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAyB5C,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CASnF;AA+GD,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAU5I"}
1
+ {"version":3,"file":"h2-pool.d.ts","sourceRoot":"","sources":["../../core/h2-pool.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAc5C,eAAO,MAAM,qBAAqB,qBAAqB,CAAA;AA2BvD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CASnF;AAKD,wBAAgB,4BAA4B,CACxC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,0BAA0B,KAAK,KAAK,CAAC,kBAAkB,CAAC,GAAG,IAAI,GACvG,IAAI,CAEN;AA2HD,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAU5I"}
@@ -36,7 +36,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.UPSTREAM_TIMEOUT_CODE = void 0;
39
40
  exports.cleanHeadersForH2 = cleanHeadersForH2;
41
+ exports.__setH2SessionFactoryForTest = __setH2SessionFactoryForTest;
40
42
  exports.makeProxyRequest = makeProxyRequest;
41
43
  const http = __importStar(require("http"));
42
44
  const https = __importStar(require("https"));
@@ -45,7 +47,18 @@ const debug_1 = __importDefault(require("debug"));
45
47
  const proxyDebug = (0, debug_1.default)('proxy');
46
48
  const h2SessionPool = new Map();
47
49
  const MAX_H2_SESSIONS = process.env.MEDDLE_MAX_H2_SESSIONS ? parseInt(process.env.MEDDLE_MAX_H2_SESSIONS) : 32;
48
- const UPSTREAM_REQUEST_TIMEOUT_MS = 60000;
50
+ const DEFAULT_UPSTREAM_REQUEST_TIMEOUT_MS = 60000;
51
+ function upstreamTimeoutMs() {
52
+ const fromEnv = process.env.MEDDLE_UPSTREAM_TIMEOUT_MS;
53
+ const parsed = fromEnv ? parseInt(fromEnv, 10) : NaN;
54
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_UPSTREAM_REQUEST_TIMEOUT_MS;
55
+ }
56
+ exports.UPSTREAM_TIMEOUT_CODE = 'UPSTREAM_TIMEOUT';
57
+ function upstreamTimeoutError(target, timeoutMs) {
58
+ const err = new Error(`上游连接超时: ${timeoutMs / 1000}s 内未收到 ${target} 的响应(上游服务可能挂起、网络不通或规则指向了不可达目标)`);
59
+ err.code = exports.UPSTREAM_TIMEOUT_CODE;
60
+ return err;
61
+ }
49
62
  function evictOldestH2SessionIfNeeded() {
50
63
  while (h2SessionPool.size >= MAX_H2_SESSIONS) {
51
64
  const oldestKey = h2SessionPool.keys().next().value;
@@ -80,6 +93,15 @@ function cleanHeadersForH2(headers) {
80
93
  }
81
94
  return cleaned;
82
95
  }
96
+ // Test-only hook: override the session constructor so specs can inject a fake
97
+ // http2 session (e.g. one that emits 'error' repeatedly). Do not call from prod.
98
+ let sessionFactoryOverride = null;
99
+ function __setH2SessionFactoryForTest(factory) {
100
+ sessionFactoryOverride = factory;
101
+ }
102
+ function createH2Session(origin, opts) {
103
+ return sessionFactoryOverride ? sessionFactoryOverride(origin, opts) : http2.connect(origin, opts);
104
+ }
83
105
  function getOrCreateH2Session(origin, servername) {
84
106
  const poolKey = servername ? `${origin}#${servername}` : origin;
85
107
  const cached = h2SessionPool.get(poolKey);
@@ -91,10 +113,12 @@ function getOrCreateH2Session(origin, servername) {
91
113
  const connectOpts = { rejectUnauthorized: false };
92
114
  if (servername)
93
115
  connectOpts.servername = servername;
94
- const session = http2.connect(origin, connectOpts);
116
+ const session = createH2Session(origin, connectOpts);
95
117
  const timeout = setTimeout(() => {
96
118
  session.destroy();
97
- reject(new Error('HTTP/2 connection timeout'));
119
+ const err = new Error(`HTTP/2 连接超时: 5s 内未能与 ${origin} 建立 HTTP/2 连接(上游不支持 H2 或网络不通)`);
120
+ err.code = 'H2_CONNECT_TIMEOUT';
121
+ reject(err);
98
122
  }, 5000);
99
123
  session.once('connect', () => {
100
124
  clearTimeout(timeout);
@@ -102,10 +126,17 @@ function getOrCreateH2Session(origin, servername) {
102
126
  h2SessionPool.set(poolKey, session);
103
127
  resolve(session);
104
128
  });
105
- session.once('error', (err) => {
129
+ // Persistent error listener: a session that errored once must be
130
+ // evicted AND destroyed so it is never reused. Using `once` would drop
131
+ // the listener after the first error — a second error (e.g. an RST
132
+ // while a concurrent caller still holds the session) had no listener
133
+ // and crashed the whole process via uncaughtException.
134
+ session.on('error', (err) => {
106
135
  clearTimeout(timeout);
107
136
  h2SessionPool.delete(poolKey);
108
- reject(err);
137
+ if (!session.destroyed)
138
+ session.destroy();
139
+ reject(err); // no-op after the promise has settled
109
140
  });
110
141
  session.on('close', () => { h2SessionPool.delete(poolKey); });
111
142
  session.on('goaway', () => {
@@ -178,8 +209,8 @@ function proxyViaH1(target, method, headers, reqBody) {
178
209
  protocol: 'h1.1'
179
210
  });
180
211
  });
181
- proxyReq.setTimeout(UPSTREAM_REQUEST_TIMEOUT_MS, () => {
182
- proxyReq.destroy(new Error('HTTP/1.1 upstream request timeout'));
212
+ proxyReq.setTimeout(upstreamTimeoutMs(), () => {
213
+ proxyReq.destroy(upstreamTimeoutError(target, upstreamTimeoutMs()));
183
214
  });
184
215
  proxyReq.on('error', reject);
185
216
  if (reqBody && reqBody.length > 0)
@@ -1 +1 @@
1
- {"version":3,"file":"h2-pool.js","sourceRoot":"","sources":["../../core/h2-pool.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,8CASC;AA+GD,4CAUC;AA/JD,2CAA4B;AAC5B,6CAA8B;AAC9B,6CAA8B;AAC9B,kDAA0B;AAG1B,MAAM,UAAU,GAAG,IAAA,eAAM,EAAC,OAAO,CAAC,CAAA;AAElC,MAAM,aAAa,GAAG,IAAI,GAAG,EAAoC,CAAA;AACjE,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AAC9G,MAAM,2BAA2B,GAAG,KAAK,CAAA;AAEzC,SAAS,4BAA4B;IACjC,OAAO,aAAa,CAAC,IAAI,IAAI,eAAe,EAAE,CAAC;QAC3C,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAA;QACnD,IAAI,SAAS,KAAK,SAAS;YAAE,MAAK;QAClC,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QAC3C,aAAa,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;QAC/B,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YAC9B,IAAI,CAAC;gBAAC,MAAM,CAAC,KAAK,EAAE,CAAA;YAAC,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBAAC,IAAI,CAAC;oBAAC,MAAM,CAAC,OAAO,EAAE,CAAA;gBAAC,CAAC;gBAAC,OAAO,CAAC,EAAE,CAAC,CAAA,CAAC;YAAC,CAAC;QAC9E,CAAC;IACL,CAAC;AACL,CAAC;AAED,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IAC/B,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,qBAAqB;IACvE,IAAI,EAAE,SAAS,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM;CAC1D,CAAC,CAAA;AAEF,SAAgB,iBAAiB,CAAC,OAA4B;IAC1D,MAAM,OAAO,GAAwB,EAAE,CAAA;IACvC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACjD,MAAM,EAAE,GAAG,GAAG,CAAC,WAAW,EAAE,CAAA;QAC5B,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACrD,OAAO,CAAC,EAAE,CAAC,GAAG,KAAK,CAAA;QACvB,CAAC;IACL,CAAC;IACD,OAAO,OAAO,CAAA;AAClB,CAAC;AAED,SAAS,oBAAoB,CAAC,MAAc,EAAE,UAAmB;IAC7D,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,UAAU,EAAE,CAAC,CAAC,CAAC,MAAM,CAAA;IAC/D,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;IACzC,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QAChD,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IAClC,CAAC;IACD,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAE7B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnC,MAAM,WAAW,GAAqC,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAA;QACnF,IAAI,UAAU;YAAE,WAAW,CAAC,UAAU,GAAG,UAAU,CAAA;QACnD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;QAElD,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,OAAO,CAAC,OAAO,EAAE,CAAA;YACjB,MAAM,CAAC,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC,CAAA;QAClD,CAAC,EAAE,IAAI,CAAC,CAAA;QAER,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE;YACzB,YAAY,CAAC,OAAO,CAAC,CAAA;YACrB,4BAA4B,EAAE,CAAA;YAC9B,aAAa,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;YACnC,OAAO,CAAC,OAAO,CAAC,CAAA;QACpB,CAAC,CAAC,CAAA;QAEF,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;YACjC,YAAY,CAAC,OAAO,CAAC,CAAA;YACrB,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YAC7B,MAAM,CAAC,GAAG,CAAC,CAAA;QACf,CAAC,CAAC,CAAA;QAEF,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;QAC5D,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;YACtB,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YAC7B,IAAI,CAAC,OAAO,CAAC,SAAS;gBAAE,OAAO,CAAC,OAAO,EAAE,CAAA;QAC7C,CAAC,CAAC,CAAA;QACF,OAAO,CAAC,UAAU,CAAC,KAAK,EAAE,GAAG,EAAE;YAC3B,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YAC7B,IAAI,CAAC,OAAO,CAAC,SAAS;gBAAE,OAAO,CAAC,KAAK,EAAE,CAAA;QAC3C,CAAC,CAAC,CAAA;IACN,CAAC,CAAC,CAAA;AACN,CAAC;AAED,SAAS,UAAU,CAAC,MAAc,EAAE,MAAc,EAAE,OAA4B,EAAE,OAAe;IAC7F,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAA;IAC3B,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAA;IACzB,MAAM,YAAY,GAAW,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,IAAI,CAAA;IAC9F,MAAM,gBAAgB,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IACnD,MAAM,UAAU,GAAG,CAAC,GAAG,CAAC,QAAQ,KAAK,gBAAgB,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,SAAS,CAAA;IAErF,OAAO,oBAAoB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;QAC3D,OAAO,IAAI,OAAO,CAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAClD,IAAI,CAAC;gBACD,MAAM,SAAS,GAAwB,iBAAiB,CAAC,OAAO,CAAC,CAAA;gBACjE,SAAS,CAAC,SAAS,CAAC,GAAG,MAAM,CAAA;gBAC7B,SAAS,CAAC,OAAO,CAAC,GAAG,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAA;gBAC9C,SAAS,CAAC,YAAY,CAAC,GAAG,YAAY,CAAA;gBACtC,SAAS,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;gBAEpD,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;gBAC3C,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,UAAqC,EAAE,EAAE;oBAC9D,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAA;oBAChD,MAAM,KAAK,GAAwB,EAAE,CAAA;oBACrC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;wBAC9C,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;4BAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;oBACxC,CAAC;oBACD,OAAO,CAAC,EAAE,UAAU,EAAE,aAAa,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;gBAChG,CAAC,CAAC,CAAA;gBACF,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;gBAC5B,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;oBAAE,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;gBAC1D,QAAQ,CAAC,GAAG,EAAE,CAAA;YAClB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YAAC,CAAC;QACjC,CAAC,CAAC,CAAA;IACN,CAAC,CAAC,CAAA;AACN,CAAC;AAED,SAAS,UAAU,CAAC,MAAc,EAAE,MAAc,EAAE,OAA4B,EAAE,OAAe;IAC7F,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAA;IAC3B,MAAM,SAAS,GAAG,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAA;IAE1E,MAAM,SAAS,GAAwB,EAAE,CAAA;IACzC,MAAM,YAAY,GAAW,OAAO,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,CAAA;IAC9F,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACjD,MAAM,UAAU,GAAG,GAAG,CAAC,WAAW,EAAE,CAAA;QACpC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,UAAU,KAAK,qBAAqB,IAAI,UAAU,KAAK,kBAAkB,EAAE,CAAC;YACpG,SAAS,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;QAC1B,CAAC;IACL,CAAC;IACD,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI;QAAE,SAAS,CAAC,IAAI,GAAG,YAAY,CAAA;IAErE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnC,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,KAAK,EAAS,EAAE,CAAC,QAA8B,EAAE,EAAE;YACpI,OAAO,CAAC;gBACJ,UAAU,EAAE,QAAQ,CAAC,UAAW;gBAChC,aAAa,EAAE,QAAQ,CAAC,aAAa,IAAI,EAAE;gBAC3C,OAAO,EAAE,QAAQ,CAAC,OAA8B;gBAChD,MAAM,EAAE,QAAQ;gBAChB,QAAQ,EAAE,MAAM;aACnB,CAAC,CAAA;QACN,CAAC,CAAC,CAAA;QACF,QAAQ,CAAC,UAAU,CAAC,2BAA2B,EAAE,GAAG,EAAE;YAClD,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC,CAAA;QACpE,CAAC,CAAC,CAAA;QACF,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAC5B,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QAC1D,QAAQ,CAAC,GAAG,EAAE,CAAA;IAClB,CAAC,CAAC,CAAA;AACN,CAAC;AAEM,KAAK,UAAU,gBAAgB,CAAC,MAAc,EAAE,MAAc,EAAE,OAA4B,EAAE,OAAe;IAChH,IAAI,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7B,IAAI,CAAC;YACD,OAAO,MAAM,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;QAC7D,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAChB,UAAU,CAAC,2BAA2B,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;YACpD,OAAO,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;QACvD,CAAC;IACL,CAAC;IACD,OAAO,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;AACvD,CAAC"}
1
+ {"version":3,"file":"h2-pool.js","sourceRoot":"","sources":["../../core/h2-pool.ts"],"names":[],"mappings":"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"}
package/index.js CHANGED
@@ -22,6 +22,8 @@ const {
22
22
  } = require('./dist/core/client-identity')
23
23
  const { createApplicationIdentityResolver } = require('./dist/core/application-identity')
24
24
  const { runAsyncUpdateCheck } = require('./bin/lib/update-check')
25
+ const { resolveMeddleHome } = require('./bin/lib/meddle-home')
26
+ const { recordCrash, crashFingerprint } = require('./bin/lib/crash-report')
25
27
  const {
26
28
  authorizeProxyClient,
27
29
  buildRemoteAccessConfig,
@@ -37,7 +39,10 @@ const chalk = require('chalk')
37
39
  const _debug = require('debug')
38
40
 
39
41
  const proxyDebug = _debug('proxy')
40
- const UPSTREAM_REQUEST_TIMEOUT_MS = 60000
42
+ const UPSTREAM_REQUEST_TIMEOUT_MS = process.env.MEDDLE_UPSTREAM_TIMEOUT_MS
43
+ ? parseInt(process.env.MEDDLE_UPSTREAM_TIMEOUT_MS, 10)
44
+ : 60000
45
+ const UPSTREAM_TIMEOUT_CODE = 'UPSTREAM_TIMEOUT'
41
46
  const MITM_SERVER_IDLE_TTL_MS = 10 * 60 * 1000
42
47
  const MITM_SERVER_SWEEP_INTERVAL_MS = 60 * 1000
43
48
  const MAX_WS_BUFFERED_MESSAGES = 1000
@@ -463,11 +468,17 @@ const proxyServer = http.createServer(async (req, res) => {
463
468
  })
464
469
  })
465
470
  proxyReq.setTimeout(UPSTREAM_REQUEST_TIMEOUT_MS, () => {
466
- proxyReq.destroy(new Error('HTTP upstream request timeout'))
471
+ const err = new Error(`上游连接超时: ${UPSTREAM_REQUEST_TIMEOUT_MS / 1000}s 内未收到 ${url.href} 的响应`)
472
+ err.code = UPSTREAM_TIMEOUT_CODE
473
+ proxyReq.destroy(err)
467
474
  })
468
475
  proxyReq.on('error', (err) => {
469
476
  pluginIntercept.emitLegacyErrorToPlugins('onBeforeResponse', err)
470
- logRuntimeError(`http:proxy-request:${getErrorKey(err)}`, 'HTTP proxy error:', getErrorMessage(err))
477
+ if (err && err.code === UPSTREAM_TIMEOUT_CODE) {
478
+ console.warn(`[proxy:upstream-timeout] ${getErrorMessage(err)}`)
479
+ } else {
480
+ logRuntimeError(`http:proxy-request:${getErrorKey(err)}`, 'HTTP proxy error:', getErrorMessage(err))
481
+ }
471
482
  finishResponseWithProxyError(res)
472
483
  })
473
484
  proxyReq.write(reqBody)
@@ -719,10 +730,12 @@ proxyServer.on('connect', async (req, socket, head) => {
719
730
  } catch (err) {
720
731
  const code = err.code || ''
721
732
  const isConnReset = code === 'ECONNRESET' || code === 'ETIMEDOUT' || code === 'ECONNREFUSED'
722
- if (isConnReset) {
733
+ if (code === UPSTREAM_TIMEOUT_CODE) {
734
+ console.warn(`[proxy:upstream-timeout] ${getErrorMessage(err)} → ${originHost}${req.url}`)
735
+ } else if (isConnReset) {
723
736
  logRuntimeError(`https:upstream:${originHost}:${code}`, '[proxy] upstream %s %s: %s', originHost + req.url, code, err.message)
724
737
  } else {
725
- logRuntimeError(`https:upstream:${originHost}:${getErrorKey(err)}`, '[error debug]', originHost + req.url, err)
738
+ logRuntimeError(`https:upstream:${originHost}:${getErrorKey(err)}`, '[proxy] upstream %s: %s', originHost + req.url, getErrorMessage(err))
726
739
  }
727
740
  pluginIntercept.emitLegacyErrorToPlugins('onBeforeResponse', err)
728
741
  finishResponseWithProxyError(res)
@@ -737,6 +750,15 @@ proxyServer.on('connect', async (req, socket, head) => {
737
750
  markMitmServerUsed(server)
738
751
  server._epActiveSockets = new Set()
739
752
 
753
+ // A client resetting its TLS connection (ECONNRESET) can surface
754
+ // as an 'error' on the http2 server. Without a listener this
755
+ // would propagate to uncaughtException and kill the whole proxy.
756
+ server.on('error', (err) => {
757
+ if (!isExpectedSocketError(err)) {
758
+ proxyDebug('MITM server error', originHost, getErrorMessage(err))
759
+ }
760
+ })
761
+
740
762
  server._epRegisterClientIdentity = (remotePort, identity) => {
741
763
  clientIdentityRegistry.register(remotePort, identity)
742
764
  }
@@ -957,6 +979,28 @@ function handleFatalError(type, reason) {
957
979
  if (!fatalErrorInProgress) {
958
980
  fatalErrorInProgress = true
959
981
  writeFatalError(type, reason)
982
+
983
+ // 崩溃采集: 落盘现场(指纹去重、脱敏),便于事后定位
984
+ const err = reason instanceof Error ? reason : new Error(String(reason))
985
+ try {
986
+ const home = resolveMeddleHome()
987
+ const crashFile = recordCrash({
988
+ home,
989
+ error: err,
990
+ version: require('./package.json').version,
991
+ envKeys: Object.keys(process.env || {}),
992
+ context: {
993
+ fatalType: type,
994
+ connections: activeProxySockets.size,
995
+ mitmServers: ctx.httpsServerMap ? ctx.httpsServerMap.size : 0,
996
+ },
997
+ })
998
+ if (crashFile) {
999
+ console.error(`[fatal] 崩溃报告已保存: ${crashFile} (指纹 ${crashFingerprint(err)})`)
1000
+ }
1001
+ } catch (_) {
1002
+ // 崩溃报告失败不影响退出
1003
+ }
960
1004
  }
961
1005
 
962
1006
  process.exit(1)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jonathanleelx/meddle",
3
- "version": "0.4.1-beta.2",
3
+ "version": "0.4.2",
4
4
  "description": "A simple development proxy server with custom routing rules, mock responses, HTTPS interception, and a plugin system",
5
5
  "type": "commonjs",
6
6
  "main": "index.js",