| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- const { io } = require('socket.io-client');
- const http = require('http');
- const debug = require('debug')('tunnel:client');
- class SocketTunnel {
- constructor(options) {
- this.options = {
- serverUrl: options.serverUrl || process.env.TUNNEL_SERVER || 'http://localhost:23909',
- localPort: options.localPort || parseInt(process.env.LOCAL_PORT || '1081')
- };
- this.maxRetries = options.maxRetries || parseInt(process.env.MAX_RETRIES || '10');
- this.socket = null;
- this.tunnelId = null;
- this.url = null;
- this.connected = false;
- this.retryCount = 0;
- }
- async connect() {
- try {
- this.socket = io(this.options.serverUrl);
- this.socket.on('connect', () => {
- debug('已连接到服务器');
- this.connected = true;
- this.retryCount = 0;
- // 注册隧道
- this.socket.emit('register', {
- localPort: this.options.localPort
- });
- });
- this.socket.on('registered', (data) => {
- this.tunnelId = data.tunnelId;
- this.url = data.url;
- debug('隧道已注册:', this.url);
- });
- // 处理请求
- this.socket.on('request', async (data) => {
- debug('收到请求:', data.method, data.path);
-
- // 转发到本地服务器
- const response = await this.forwardRequest(data);
-
- this.socket.emit('response', {
- requestId: data.id,
- response
- });
- });
- // 心跳检测
- setInterval(() => {
- if (this.connected) {
- this.socket.emit('ping');
- }
- }, 30000);
- this.socket.on('disconnect', () => {
- debug('与服务器断开连接');
- this.connected = false;
- this.reconnect();
- });
- this.socket.on('error', (err) => {
- debug('连接错误:', err);
- this.reconnect();
- });
- } catch (err) {
- debug('连接失败:', err);
- this.reconnect();
- }
- }
- async forwardRequest(data) {
- return new Promise((resolve) => {
- const options = {
- hostname: 'localhost',
- port: this.options.localPort,
- path: data.path || '/', // 确保路径存在
- method: data.method,
- headers: {
- ...data.headers,
- host: 'localhost:' + this.options.localPort // 修改 host 头
- }
- };
- debug('转发请求到本地服务器:', options.method, options.path);
- const req = http.request(options, (res) => {
- const chunks = [];
- res.on('data', chunk => chunks.push(chunk));
- res.on('end', () => {
- const body = Buffer.concat(chunks).toString();
- debug('本地服务器响应:', res.statusCode);
- resolve({
- status: res.statusCode,
- headers: res.headers,
- body
- });
- });
- });
- req.on('error', (err) => {
- debug('本地请求错误:', err);
- resolve({
- status: 502,
- headers: { 'content-type': 'text/plain' },
- body: 'Bad Gateway'
- });
- });
- req.setTimeout(5000, () => {
- debug('本地请求超时');
- req.destroy();
- resolve({
- status: 504,
- headers: { 'content-type': 'text/plain' },
- body: 'Gateway Timeout'
- });
- });
- // 处理请求体
- if (data.body) {
- try {
- // 如果是对象,转换为 JSON 字符串
- const body = typeof data.body === 'object'
- ? JSON.stringify(data.body)
- : data.body.toString();
-
- req.write(body);
- debug('已写入请求体');
- } catch (err) {
- debug('写入请求体失败:', err);
- }
- }
- req.end();
- });
- }
- reconnect() {
- if (this.retryCount >= this.maxRetries) {
- debug('达到最大重试次数');
- return;
- }
- const delay = Math.min(1000 * Math.pow(2, this.retryCount), 30000);
- debug(`${delay}ms 后重试连接`);
- setTimeout(() => {
- this.retryCount++;
- this.connect();
- }, delay);
- }
- close() {
- if (this.socket) {
- this.socket.close();
- }
- }
- }
- module.exports = { SocketTunnel };
|