lepao-proxy-tls-test.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. #!/usr/bin/env node
  2. 'use strict'
  3. /**
  4. * 在学校服务器上测试「直连 vs HTTP 代理」访问 lepao HTTPS 时 TLS 是否正常。
  5. *
  6. * 用法(在项目根 ic-ctbu-backend 下执行):
  7. * node scripts/lepao-proxy-tls-test.js direct
  8. * node scripts/lepao-proxy-tls-test.js proxy 103.217.191.35:20028
  9. * node scripts/lepao-proxy-tls-test.js qg-fetch # 用 config.json 调青果 /get 取 1 个节点再测
  10. *
  11. * 环境变量(可选):
  12. * TEST_HTTPS_URL 默认 https://lepao.ctbu.edu.cn/(也可用具体 API,如 …/WpIndex/getOssSts)
  13. * TEST_POST 若为 1,则对 TEST_HTTPS_URL 发 POST(body 为空 JSON),更接近业务
  14. * TEST_TIMEOUT_MS 默认 20000
  15. * QG_PROXY_USER / QG_PROXY_PASSWORD 覆盖 config 里 authUser/authPassword
  16. * QG_AREA / QG_AREA_EX / QG_ISP / QG_DISTINCT qg-fetch 时传给青果(与线上一致可用来复现)
  17. *
  18. * NODE_TLS_REJECT_UNAUTHORIZED=0 不推荐生产使用;设为 0 时本脚本与其它行为一致会做不安全 TLS
  19. */
  20. const path = require('path')
  21. const fs = require('fs')
  22. const axios = require('axios')
  23. const HttpsProxyAgent = require('https-proxy-agent')
  24. const QG_GET_URL = 'https://share.proxy.qg.net/get'
  25. function loadConfig() {
  26. const p = path.join(__dirname, '..', 'config.json')
  27. const raw = fs.readFileSync(p, 'utf8')
  28. return JSON.parse(raw)
  29. }
  30. function buildProxyUrl(server, authUser, authPassword) {
  31. const parts = String(server || '').trim().split(':')
  32. const host = parts[0]
  33. const port = parts[1]
  34. if (!host || !port) throw new Error('代理 server 需为 host:port')
  35. let userPart = ''
  36. if (authUser) {
  37. const u = encodeURIComponent(authUser)
  38. const p = encodeURIComponent(authPassword != null ? String(authPassword) : '')
  39. userPart = `${u}:${p}@`
  40. }
  41. return `http://${userPart}${host}:${port}`
  42. }
  43. async function probe(label, outbound) {
  44. const url = process.env.TEST_HTTPS_URL || 'https://lepao.ctbu.edu.cn/'
  45. const timeout = Number(process.env.TEST_TIMEOUT_MS || 20000)
  46. const usePost = String(process.env.TEST_POST || '').trim() === '1'
  47. const rejectUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '0'
  48. const base = {
  49. timeout,
  50. validateStatus: () => true,
  51. ...outbound
  52. }
  53. console.log(`\n---------- ${label} ----------`)
  54. console.log('REQUEST:', usePost ? 'POST' : 'GET', url)
  55. console.log('timeout_ms:', timeout, 'rejectUnauthorized:', rejectUnauthorized)
  56. const t0 = Date.now()
  57. try {
  58. const res = usePost
  59. ? await axios.post(url, {}, { headers: { 'Content-Type': 'application/json' }, ...base })
  60. : await axios.get(url, base)
  61. const ms = Date.now() - t0
  62. console.log('RESULT: OK', ms + 'ms', 'HTTP', res.status)
  63. const bodyPreview = typeof res.data === 'string' ? res.data : JSON.stringify(res.data)
  64. console.log('body_preview:', bodyPreview.slice(0, 300).replace(/\s+/g, ' '))
  65. } catch (e) {
  66. const ms = Date.now() - t0
  67. console.log('RESULT: FAIL', ms + 'ms')
  68. console.log('message:', e.message)
  69. console.log('code:', e.code)
  70. if (e.response) console.log('http_status:', e.response.status)
  71. }
  72. }
  73. async function fetchOneProxyFromQg(config) {
  74. const q = config.qgChannelProxy || {}
  75. const key = String(q.extractKey || '').trim()
  76. if (!key) throw new Error('config.json 缺少 qgChannelProxy.extractKey')
  77. const params = { key, num: 1 }
  78. const area = String(process.env.QG_AREA || '').trim()
  79. const areaEx = String(process.env.QG_AREA_EX || '').trim()
  80. if (area) params.area = area
  81. if (areaEx) params.area_ex = areaEx
  82. const isp = process.env.QG_ISP
  83. if (isp === '1' || isp === '2' || isp === '3') params.isp = Number(isp)
  84. if (process.env.QG_DISTINCT === '0') params.distinct = false
  85. else params.distinct = true
  86. const res = await axios.get(QG_GET_URL, {
  87. params,
  88. timeout: 20000,
  89. proxy: false,
  90. validateStatus: () => true
  91. })
  92. const body = res.data
  93. const code = body && body.code
  94. if (code !== 'SUCCESS' || !Array.isArray(body.data) || !body.data[0]) {
  95. throw new Error(`青果 /get 失败: ${code || JSON.stringify(body).slice(0, 240)}`)
  96. }
  97. const item = body.data[0]
  98. return {
  99. server: item.server,
  100. proxy_ip: item.proxy_ip,
  101. deadline: item.deadline,
  102. request_id: body.request_id
  103. }
  104. }
  105. async function main() {
  106. const mode = (process.argv[2] || 'direct').toLowerCase()
  107. const cfg = loadConfig()
  108. const q = cfg.qgChannelProxy || {}
  109. const authUser = process.env.QG_PROXY_USER || q.authUser || ''
  110. const authPassword = process.env.QG_PROXY_PASSWORD || q.authPassword || ''
  111. if (process.env.NODE_TLS_REJECT_UNAUTHORIZED === '0') {
  112. console.warn('[WARN] NODE_TLS_REJECT_UNAUTHORIZED=0,TLS 未校验服务端证书。\n')
  113. }
  114. await probe('① 直连(无代理)', { proxy: false })
  115. if (mode === 'direct') return
  116. let serverArg
  117. if (mode === 'proxy') {
  118. serverArg = process.argv[3]
  119. if (!serverArg) {
  120. console.error('用法: node scripts/lepao-proxy-tls-test.js proxy host:port')
  121. process.exit(1)
  122. }
  123. } else if (mode === 'qg-fetch') {
  124. console.log('\n[qg-fetch] 正在请求青果 /get …')
  125. const got = await fetchOneProxyFromQg(cfg)
  126. serverArg = got.server
  127. console.log(
  128. '青果节点:',
  129. got.server,
  130. 'proxy_ip=',
  131. got.proxy_ip,
  132. 'deadline=',
  133. got.deadline,
  134. 'request_id=',
  135. got.request_id
  136. )
  137. if (!serverArg) throw new Error('青果返回无 server')
  138. } else {
  139. console.error('未知模式:', mode, '可用: direct | proxy <host:port> | qg-fetch')
  140. process.exit(1)
  141. }
  142. const proxyUrl = buildProxyUrl(serverArg, authUser, authPassword)
  143. const rejectUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '0'
  144. const agent = new HttpsProxyAgent(proxyUrl, { rejectUnauthorized })
  145. await probe(`② HTTP 代理 → 学校 HTTPS (${serverArg})`, { proxy: false, httpsAgent: agent })
  146. }
  147. main().catch((e) => {
  148. console.error(e.stack || e)
  149. process.exit(1)
  150. })