github.com/nxtrace/NTrace-core@v1.3.1-0.20240513132635-39169291e8c9/wshandle/client.go (about)

     1  package wshandle
     2  
     3  import (
     4  	"crypto/tls"
     5  	"github.com/nxtrace/NTrace-core/pow"
     6  	"github.com/nxtrace/NTrace-core/util"
     7  	"log"
     8  	"net"
     9  	"net/http"
    10  	"net/url"
    11  	"os"
    12  	"os/signal"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	"github.com/gorilla/websocket"
    18  )
    19  
    20  type WsConn struct {
    21  	Connecting   bool
    22  	Connected    bool            // 连接状态
    23  	MsgSendCh    chan string     // 消息发送通道
    24  	MsgReceiveCh chan string     // 消息接收通道
    25  	Done         chan struct{}   // 发送结束通道
    26  	Exit         chan bool       // 程序退出信号
    27  	Interrupt    chan os.Signal  // 终端中止信号
    28  	Conn         *websocket.Conn // 主连接
    29  	ConnMux      sync.Mutex      // 连接互斥锁
    30  }
    31  
    32  var wsconn *WsConn
    33  var host, port, fastIp string
    34  var envToken = util.EnvToken
    35  var cacheToken string
    36  var cacheTokenFailedTimes int
    37  
    38  func (c *WsConn) keepAlive() {
    39  	go func() {
    40  		// 开启一个定时器
    41  		for {
    42  			<-time.After(time.Second * 54)
    43  			if c.Connected {
    44  				err := c.Conn.WriteMessage(websocket.TextMessage, []byte("ping"))
    45  				if err != nil {
    46  					log.Println(err)
    47  					c.Connected = false
    48  					return
    49  				}
    50  			}
    51  		}
    52  	}()
    53  	for {
    54  		if !c.Connected && !c.Connecting {
    55  			c.Connecting = true
    56  			c.recreateWsConn()
    57  			// log.Println("WebSocket 连接意外断开,正在尝试重连...")
    58  			// return
    59  		}
    60  		// 降低检测频率,优化 CPU 占用情况
    61  		<-time.After(200 * time.Millisecond)
    62  	}
    63  }
    64  
    65  func (c *WsConn) messageReceiveHandler() {
    66  	// defer close(c.Done)
    67  	for {
    68  		if c.Connected {
    69  			_, msg, err := c.Conn.ReadMessage()
    70  			if err != nil {
    71  				// 读取信息出错,连接已经意外断开
    72  				// log.Println(err)
    73  				c.Connected = false
    74  				return
    75  			}
    76  			if string(msg) != "pong" {
    77  				c.MsgReceiveCh <- string(msg)
    78  			}
    79  		}
    80  	}
    81  }
    82  
    83  func (c *WsConn) messageSendHandler() {
    84  	for {
    85  		// 循环监听发送
    86  		select {
    87  		case <-c.Done:
    88  			log.Println("go-routine has been returned")
    89  			return
    90  		case t := <-c.MsgSendCh:
    91  			// log.Println(t)
    92  			if !c.Connected {
    93  				c.MsgReceiveCh <- `{"ip":"` + t + `", "asnumber":"API Server Error"}`
    94  			} else {
    95  				err := c.Conn.WriteMessage(websocket.TextMessage, []byte(t))
    96  				if err != nil {
    97  					log.Println("write:", err)
    98  					return
    99  				}
   100  			}
   101  		// 来自终端的中断运行请求
   102  		case <-c.Interrupt:
   103  			// 向 websocket 发起关闭连接任务
   104  			err := c.Conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
   105  			if err != nil {
   106  				// log.Println("write close:", err)
   107  				os.Exit(1)
   108  			}
   109  			select {
   110  			// 等到了结果,直接退出
   111  			case <-c.Done:
   112  			// 如果等待 1s 还是拿不到结果,不再等待,超时退出
   113  			case <-time.After(time.Second):
   114  			}
   115  			os.Exit(1)
   116  			// return
   117  		}
   118  	}
   119  }
   120  
   121  func (c *WsConn) recreateWsConn() {
   122  	// 尝试重新连线
   123  	u := url.URL{Scheme: "wss", Host: fastIp + ":" + port, Path: "/v3/ipGeoWs"}
   124  	// log.Printf("connecting to %s", u.String())
   125  	jwtToken, ua := envToken, []string{"Privileged Client"}
   126  	err := error(nil)
   127  	if envToken == "" {
   128  		// 无环境变量 token
   129  		if cacheToken == "" {
   130  			// 无cacheToken, 重新获取 token
   131  			if util.GetPowProvider() == "" {
   132  				jwtToken, err = pow.GetToken(fastIp, host, port)
   133  			} else {
   134  				jwtToken, err = pow.GetToken(util.GetPowProvider(), util.GetPowProvider(), port)
   135  			}
   136  			if err != nil {
   137  				log.Println(err)
   138  				os.Exit(1)
   139  			}
   140  		} else {
   141  			// 使用 cacheToken
   142  			jwtToken = cacheToken
   143  		}
   144  		ua = []string{util.UserAgent}
   145  	}
   146  	cacheToken = jwtToken
   147  	requestHeader := http.Header{
   148  		"Host":          []string{host},
   149  		"User-Agent":    ua,
   150  		"Authorization": []string{"Bearer " + jwtToken},
   151  	}
   152  	dialer := websocket.DefaultDialer
   153  	dialer.TLSClientConfig = &tls.Config{
   154  		ServerName: host,
   155  	}
   156  	proxyUrl := util.GetProxy()
   157  	if proxyUrl != nil {
   158  		dialer.Proxy = http.ProxyURL(proxyUrl)
   159  	}
   160  	ws, _, err := websocket.DefaultDialer.Dial(u.String(), requestHeader)
   161  	c.Conn = ws
   162  	if err != nil {
   163  		log.Println("dial:", err)
   164  		// <-time.After(time.Second * 1)
   165  		c.Connected = false
   166  		c.Connecting = false
   167  		if cacheTokenFailedTimes > 3 {
   168  			cacheToken = ""
   169  		}
   170  		cacheTokenFailedTimes += 1
   171  		//fmt.Println("重连失败", cacheTokenFailedTimes, "次")
   172  		return
   173  	} else {
   174  		c.Connected = true
   175  	}
   176  	c.Connecting = false
   177  
   178  	c.Done = make(chan struct{})
   179  	go c.messageReceiveHandler()
   180  }
   181  
   182  func createWsConn() *WsConn {
   183  	proxyUrl := util.GetProxy()
   184  	//fmt.Println("正在连接 WS")
   185  	// 设置终端中断通道
   186  	interrupt := make(chan os.Signal, 1)
   187  	signal.Notify(interrupt, os.Interrupt)
   188  	host, port = util.GetHostAndPort()
   189  	// 如果 host 是一个 IP 使用默认域名
   190  	if valid := net.ParseIP(host); valid != nil {
   191  		fastIp = host
   192  		if len(strings.Split(fastIp, ":")) > 1 {
   193  			fastIp = "[" + fastIp + "]"
   194  		}
   195  		host = "origin-fallback.nxtrace.org"
   196  	} else {
   197  		// 默认配置完成,开始寻找最优 IP
   198  		fastIp = util.GetFastIP(host, port, true)
   199  	}
   200  	jwtToken, ua := envToken, []string{"Privileged Client"}
   201  	err := error(nil)
   202  	if envToken == "" {
   203  		if util.GetPowProvider() == "" {
   204  			jwtToken, err = pow.GetToken(fastIp, host, port)
   205  		} else {
   206  			jwtToken, err = pow.GetToken(util.GetPowProvider(), util.GetPowProvider(), port)
   207  		}
   208  		if err != nil {
   209  			log.Println(err)
   210  			os.Exit(1)
   211  		}
   212  		ua = []string{util.UserAgent}
   213  	}
   214  	cacheToken = jwtToken
   215  	cacheTokenFailedTimes = 0
   216  	requestHeader := http.Header{
   217  		"Host":          []string{host},
   218  		"User-Agent":    ua,
   219  		"Authorization": []string{"Bearer " + jwtToken},
   220  	}
   221  	dialer := websocket.DefaultDialer
   222  	dialer.TLSClientConfig = &tls.Config{
   223  		ServerName: host,
   224  	}
   225  	if proxyUrl != nil {
   226  		dialer.Proxy = http.ProxyURL(proxyUrl)
   227  	}
   228  	u := url.URL{Scheme: "wss", Host: fastIp + ":" + port, Path: "/v3/ipGeoWs"}
   229  	// log.Printf("connecting to %s", u.String())
   230  
   231  	c, _, err := websocket.DefaultDialer.Dial(u.String(), requestHeader)
   232  
   233  	wsconn = &WsConn{
   234  		Conn:         c,
   235  		Connected:    true,
   236  		Connecting:   false,
   237  		MsgSendCh:    make(chan string, 10),
   238  		MsgReceiveCh: make(chan string, 10),
   239  	}
   240  
   241  	if err != nil {
   242  		log.Println("dial:", err)
   243  		// <-time.After(time.Second * 1)
   244  		wsconn.Connected = false
   245  		wsconn.Done = make(chan struct{})
   246  		go wsconn.keepAlive()
   247  		go wsconn.messageSendHandler()
   248  		return wsconn
   249  	}
   250  	// defer c.Close()
   251  	// 将连接写入WsConn,方便随时可取
   252  	wsconn.Done = make(chan struct{})
   253  	go wsconn.keepAlive()
   254  	go wsconn.messageReceiveHandler()
   255  	go wsconn.messageSendHandler()
   256  	return wsconn
   257  }
   258  
   259  func New() *WsConn {
   260  	return createWsConn()
   261  }
   262  
   263  func GetWsConn() *WsConn {
   264  	return wsconn
   265  }