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