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