client.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. const { io } = require('socket.io-client');
  2. const http = require('http');
  3. const debug = require('debug')('tunnel:client');
  4. class SocketTunnel {
  5. constructor(options) {
  6. this.options = {
  7. serverUrl: options.serverUrl || process.env.TUNNEL_SERVER || 'http://localhost:23909',
  8. localPort: options.localPort || parseInt(process.env.LOCAL_PORT || '1081')
  9. };
  10. this.maxRetries = options.maxRetries || parseInt(process.env.MAX_RETRIES || '10');
  11. this.socket = null;
  12. this.tunnelId = null;
  13. this.url = null;
  14. this.connected = false;
  15. this.retryCount = 0;
  16. }
  17. async connect() {
  18. try {
  19. this.socket = io(this.options.serverUrl);
  20. this.socket.on('connect', () => {
  21. debug('已连接到服务器');
  22. this.connected = true;
  23. this.retryCount = 0;
  24. // 注册隧道
  25. this.socket.emit('register', {
  26. localPort: this.options.localPort
  27. });
  28. });
  29. this.socket.on('registered', (data) => {
  30. this.tunnelId = data.tunnelId;
  31. this.url = data.url;
  32. debug('隧道已注册:', this.url);
  33. });
  34. // 处理请求
  35. this.socket.on('request', async (data) => {
  36. debug('收到请求:', data.method, data.path);
  37. // 转发到本地服务器
  38. const response = await this.forwardRequest(data);
  39. this.socket.emit('response', {
  40. requestId: data.id,
  41. response
  42. });
  43. });
  44. // 心跳检测
  45. setInterval(() => {
  46. if (this.connected) {
  47. this.socket.emit('ping');
  48. }
  49. }, 30000);
  50. this.socket.on('disconnect', () => {
  51. debug('与服务器断开连接');
  52. this.connected = false;
  53. this.reconnect();
  54. });
  55. this.socket.on('error', (err) => {
  56. debug('连接错误:', err);
  57. this.reconnect();
  58. });
  59. } catch (err) {
  60. debug('连接失败:', err);
  61. this.reconnect();
  62. }
  63. }
  64. async forwardRequest(data) {
  65. return new Promise((resolve) => {
  66. const options = {
  67. hostname: 'localhost',
  68. port: this.options.localPort,
  69. path: data.path || '/', // 确保路径存在
  70. method: data.method,
  71. headers: {
  72. ...data.headers,
  73. host: 'localhost:' + this.options.localPort // 修改 host 头
  74. }
  75. };
  76. debug('转发请求到本地服务器:', options.method, options.path);
  77. const req = http.request(options, (res) => {
  78. const chunks = [];
  79. res.on('data', chunk => chunks.push(chunk));
  80. res.on('end', () => {
  81. const body = Buffer.concat(chunks).toString();
  82. debug('本地服务器响应:', res.statusCode);
  83. resolve({
  84. status: res.statusCode,
  85. headers: res.headers,
  86. body
  87. });
  88. });
  89. });
  90. req.on('error', (err) => {
  91. debug('本地请求错误:', err);
  92. resolve({
  93. status: 502,
  94. headers: { 'content-type': 'text/plain' },
  95. body: 'Bad Gateway'
  96. });
  97. });
  98. req.setTimeout(5000, () => {
  99. debug('本地请求超时');
  100. req.destroy();
  101. resolve({
  102. status: 504,
  103. headers: { 'content-type': 'text/plain' },
  104. body: 'Gateway Timeout'
  105. });
  106. });
  107. // 处理请求体
  108. if (data.body) {
  109. try {
  110. // 如果是对象,转换为 JSON 字符串
  111. const body = typeof data.body === 'object'
  112. ? JSON.stringify(data.body)
  113. : data.body.toString();
  114. req.write(body);
  115. debug('已写入请求体');
  116. } catch (err) {
  117. debug('写入请求体失败:', err);
  118. }
  119. }
  120. req.end();
  121. });
  122. }
  123. reconnect() {
  124. if (this.retryCount >= this.maxRetries) {
  125. debug('达到最大重试次数');
  126. return;
  127. }
  128. const delay = Math.min(1000 * Math.pow(2, this.retryCount), 30000);
  129. debug(`${delay}ms 后重试连接`);
  130. setTimeout(() => {
  131. this.retryCount++;
  132. this.connect();
  133. }, delay);
  134. }
  135. close() {
  136. if (this.socket) {
  137. this.socket.close();
  138. }
  139. }
  140. }
  141. module.exports = { SocketTunnel };