github.com/sagernet/sing-box@v1.9.0-rc.20/common/urltest/urltest.go (about) 1 package urltest 2 3 import ( 4 "context" 5 "net" 6 "net/http" 7 "net/url" 8 "sync" 9 "time" 10 11 "github.com/sagernet/sing/common" 12 M "github.com/sagernet/sing/common/metadata" 13 N "github.com/sagernet/sing/common/network" 14 ) 15 16 type History struct { 17 Time time.Time `json:"time"` 18 Delay uint16 `json:"delay"` 19 } 20 21 type HistoryStorage struct { 22 access sync.RWMutex 23 delayHistory map[string]*History 24 updateHook chan<- struct{} 25 } 26 27 func NewHistoryStorage() *HistoryStorage { 28 return &HistoryStorage{ 29 delayHistory: make(map[string]*History), 30 } 31 } 32 33 func (s *HistoryStorage) SetHook(hook chan<- struct{}) { 34 s.updateHook = hook 35 } 36 37 func (s *HistoryStorage) LoadURLTestHistory(tag string) *History { 38 if s == nil { 39 return nil 40 } 41 s.access.RLock() 42 defer s.access.RUnlock() 43 return s.delayHistory[tag] 44 } 45 46 func (s *HistoryStorage) DeleteURLTestHistory(tag string) { 47 s.access.Lock() 48 delete(s.delayHistory, tag) 49 s.access.Unlock() 50 s.notifyUpdated() 51 } 52 53 func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) { 54 s.access.Lock() 55 s.delayHistory[tag] = history 56 s.access.Unlock() 57 s.notifyUpdated() 58 } 59 60 func (s *HistoryStorage) notifyUpdated() { 61 updateHook := s.updateHook 62 if updateHook != nil { 63 select { 64 case updateHook <- struct{}{}: 65 default: 66 } 67 } 68 } 69 70 func (s *HistoryStorage) Close() error { 71 s.updateHook = nil 72 return nil 73 } 74 75 func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) { 76 if link == "" { 77 link = "https://www.gstatic.com/generate_204" 78 } 79 linkURL, err := url.Parse(link) 80 if err != nil { 81 return 82 } 83 hostname := linkURL.Hostname() 84 port := linkURL.Port() 85 if port == "" { 86 switch linkURL.Scheme { 87 case "http": 88 port = "80" 89 case "https": 90 port = "443" 91 } 92 } 93 94 start := time.Now() 95 instance, err := detour.DialContext(ctx, "tcp", M.ParseSocksaddrHostPortStr(hostname, port)) 96 if err != nil { 97 return 98 } 99 defer instance.Close() 100 if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](instance); isEarlyConn && earlyConn.NeedHandshake() { 101 start = time.Now() 102 } 103 req, err := http.NewRequest(http.MethodHead, link, nil) 104 if err != nil { 105 return 106 } 107 client := http.Client{ 108 Transport: &http.Transport{ 109 DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { 110 return instance, nil 111 }, 112 }, 113 CheckRedirect: func(req *http.Request, via []*http.Request) error { 114 return http.ErrUseLastResponse 115 }, 116 } 117 defer client.CloseIdleConnections() 118 resp, err := client.Do(req.WithContext(ctx)) 119 if err != nil { 120 return 121 } 122 resp.Body.Close() 123 t = uint16(time.Since(start) / time.Millisecond) 124 return 125 }