Browse Source

🐞 fix: 修复代理使用问题

Pchen. 4 days ago
parent
commit
68e9f157c7

+ 1 - 20
apis/Corn/ProxySelfCheck.js

@@ -1,28 +1,9 @@
 const axios = require('axios')
 const axios = require('axios')
-const HttpsProxyAgent = require('https-proxy-agent')
 const config = require('../../config.json')
 const config = require('../../config.json')
 const API = require('../../lib/API')
 const API = require('../../lib/API')
 const { BaseStdResponse } = require('../../BaseStdResponse')
 const { BaseStdResponse } = require('../../BaseStdResponse')
 const QgProxyManager = require('../../lib/Lepao/QgProxyManager')
 const QgProxyManager = require('../../lib/Lepao/QgProxyManager')
-
-function buildAxiosOutboundConfig(fragment) {
-    if (!fragment || fragment.proxy === false || !fragment.proxy) {
-        return { proxy: false }
-    }
-    const { host, port, auth } = fragment.proxy
-    let userPart = ''
-    if (auth && String(auth.username || '').length > 0) {
-        const u = encodeURIComponent(auth.username)
-        const p = encodeURIComponent(auth.password != null ? String(auth.password) : '')
-        userPart = `${u}:${p}@`
-    }
-    const proxyUrl = `http://${userPart}${host}:${port}`
-    const rejectUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '0'
-    return {
-        proxy: false,
-        httpsAgent: new HttpsProxyAgent(proxyUrl, { rejectUnauthorized })
-    }
-}
+const { buildAxiosOutboundConfig } = require('../../lib/Lepao/outboundAxiosConfig')
 
 
 function resolveSelfEchoUrl() {
 function resolveSelfEchoUrl() {
     const base = String(config.url || '').trim()
     const base = String(config.url || '').trim()

+ 2 - 25
lib/Lepao/lepaoSchoolHttp.js

@@ -1,6 +1,6 @@
 const axios = require('axios')
 const axios = require('axios')
-const HttpsProxyAgent = require('https-proxy-agent')
 const QgProxyManager = require('./QgProxyManager')
 const QgProxyManager = require('./QgProxyManager')
+const { buildAxiosOutboundConfig } = require('./outboundAxiosConfig')
 
 
 function sleep(ms) {
 function sleep(ms) {
     return new Promise(r => setTimeout(r, ms))
     return new Promise(r => setTimeout(r, ms))
@@ -20,29 +20,6 @@ async function getOutboundWithBackoff(qgOpts, rounds = 2) {
     throw lastErr
     throw lastErr
 }
 }
 
 
-/**
- * Axios 对「HTTPS 目标 + 内置 proxy」在部分环境下会引发 ERR_FR_TOO_MANY_REDIRECTS,
- * 改用 HttpsProxyAgent 走 CONNECT 隧道。
- */
-function buildAxiosOutboundConfig(fragment) {
-    if (!fragment || fragment.proxy === false || !fragment.proxy) {
-        return { proxy: false }
-    }
-    const { host, port, auth } = fragment.proxy
-    let userPart = ''
-    if (auth && String(auth.username || '').length > 0) {
-        const u = encodeURIComponent(auth.username)
-        const p = encodeURIComponent(auth.password != null ? String(auth.password) : '')
-        userPart = `${u}:${p}@`
-    }
-    const proxyUrl = `http://${userPart}${host}:${port}`
-    const rejectUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '0'
-    return {
-        proxy: false,
-        httpsAgent: new HttpsProxyAgent(proxyUrl, { rejectUnauthorized })
-    }
-}
-
 function debugProxyEnabled() {
 function debugProxyEnabled() {
     return String(process.env.LEPAO_DEBUG_PROXY || '').trim() === '1'
     return String(process.env.LEPAO_DEBUG_PROXY || '').trim() === '1'
 }
 }
@@ -86,7 +63,7 @@ function isQgProxyEligibleFailure(err) {
     if (status === 502 || status === 503 || status === 504) return true
     if (status === 502 || status === 503 || status === 504) return true
     if (
     if (
         err.code &&
         err.code &&
-        ['ECONNRESET', 'ECONNABORTED', 'ETIMEDOUT', 'ENOTFOUND', 'EAI_AGAIN', 'ECONNREFUSED', 'EPROTO', 'ERR_FR_TOO_MANY_REDIRECTS'].includes(
+        ['ECONNRESET', 'ECONNABORTED', 'ETIMEDOUT', 'ENOTFOUND', 'EAI_AGAIN', 'ECONNREFUSED', 'EPROTO', 'ERR_FR_TOO_MANY_REDIRECTS', 'ERR_INVALID_PROTOCOL'].includes(
             err.code
             err.code
         )
         )
     ) {
     ) {

+ 44 - 0
lib/Lepao/outboundAxiosConfig.js

@@ -0,0 +1,44 @@
+/**
+ * 统一创建 HTTPS 出站代理 Agent,兼容 https-proxy-agent 各版本的导出方式。
+ */
+function resolveHttpsProxyAgentClass() {
+    const mod = require('https-proxy-agent')
+    const AgentClass = mod.HttpsProxyAgent || mod.default || mod
+    if (typeof AgentClass !== 'function') {
+        throw new Error('https-proxy-agent 未正确安装或导出格式不兼容')
+    }
+    return AgentClass
+}
+
+function createHttpsProxyAgent(proxyUrl) {
+    const HttpsProxyAgent = resolveHttpsProxyAgentClass()
+    const rejectUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '0'
+    return new HttpsProxyAgent(proxyUrl, { rejectUnauthorized })
+}
+
+/**
+ * Axios 对「HTTPS 目标 + 内置 proxy」在部分 Node 版本下会报 ERR_INVALID_PROTOCOL,
+ * 改用 HttpsProxyAgent 走 CONNECT 隧道,并显式 proxy: false。
+ */
+function buildAxiosOutboundConfig(fragment) {
+    if (!fragment || fragment.proxy === false || !fragment.proxy) {
+        return { proxy: false }
+    }
+    const { host, port, auth } = fragment.proxy
+    let userPart = ''
+    if (auth && String(auth.username || '').length > 0) {
+        const u = encodeURIComponent(auth.username)
+        const p = encodeURIComponent(auth.password != null ? String(auth.password) : '')
+        userPart = `${u}:${p}@`
+    }
+    const proxyUrl = `http://${userPart}${host}:${port}`
+    return {
+        proxy: false,
+        httpsAgent: createHttpsProxyAgent(proxyUrl)
+    }
+}
+
+module.exports = {
+    createHttpsProxyAgent,
+    buildAxiosOutboundConfig
+}

+ 4 - 20
lib/Lepao/qgOutboundAxios.js

@@ -3,8 +3,10 @@
  * 供电费、选课书单等非 Worker 场景的对外 GET/POST 使用。
  * 供电费、选课书单等非 Worker 场景的对外 GET/POST 使用。
  */
  */
 const axios = require('axios')
 const axios = require('axios')
-const HttpsProxyAgent = require('https-proxy-agent')
 const QgProxyManager = require('./QgProxyManager')
 const QgProxyManager = require('./QgProxyManager')
+const {
+    buildAxiosOutboundConfig
+} = require('./outboundAxiosConfig')
 
 
 const PROXY_FIRST_TIMEOUT_MS = 20000
 const PROXY_FIRST_TIMEOUT_MS = 20000
 
 
@@ -25,24 +27,6 @@ async function getOutboundWithBackoff(qgOpts, rounds = 2) {
     throw lastErr
     throw lastErr
 }
 }
 
 
-function buildAxiosOutboundConfig(fragment) {
-    if (!fragment || fragment.proxy === false || !fragment.proxy) {
-        return { proxy: false }
-    }
-    const { host, port, auth } = fragment.proxy
-    let userPart = ''
-    if (auth && String(auth.username || '').length > 0) {
-        const u = encodeURIComponent(auth.username)
-        const p = encodeURIComponent(auth.password != null ? String(auth.password) : '')
-        userPart = `${u}:${p}@`
-    }
-    const proxyUrl = `http://${userPart}${host}:${port}`
-    const rejectUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '0'
-    return {
-        proxy: false,
-        httpsAgent: new HttpsProxyAgent(proxyUrl, { rejectUnauthorized })
-    }
-}
 
 
 function debugProxyEnabled() {
 function debugProxyEnabled() {
     return String(process.env.LEPAO_DEBUG_PROXY || '').trim() === '1'
     return String(process.env.LEPAO_DEBUG_PROXY || '').trim() === '1'
@@ -76,7 +60,7 @@ function isQgProxyEligibleFailure(err) {
     if (status === 502 || status === 503 || status === 504) return true
     if (status === 502 || status === 503 || status === 504) return true
     if (
     if (
         err.code &&
         err.code &&
-        ['ECONNRESET', 'ECONNABORTED', 'ETIMEDOUT', 'ENOTFOUND', 'EAI_AGAIN', 'ECONNREFUSED', 'EPROTO', 'ERR_FR_TOO_MANY_REDIRECTS'].includes(
+        ['ECONNRESET', 'ECONNABORTED', 'ETIMEDOUT', 'ENOTFOUND', 'EAI_AGAIN', 'ECONNREFUSED', 'EPROTO', 'ERR_FR_TOO_MANY_REDIRECTS', 'ERR_INVALID_PROTOCOL'].includes(
             err.code
             err.code
         )
         )
     ) {
     ) {