urltest.go 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. package urltest
  2. import (
  3. "context"
  4. "net"
  5. "net/http"
  6. "net/url"
  7. "sync"
  8. "time"
  9. M "github.com/sagernet/sing/common/metadata"
  10. N "github.com/sagernet/sing/common/network"
  11. )
  12. type History struct {
  13. Time time.Time `json:"time"`
  14. Delay uint16 `json:"delay"`
  15. }
  16. type HistoryStorage struct {
  17. access sync.RWMutex
  18. delayHistory map[string]*History
  19. }
  20. func NewHistoryStorage() *HistoryStorage {
  21. return &HistoryStorage{
  22. delayHistory: make(map[string]*History),
  23. }
  24. }
  25. func (s *HistoryStorage) LoadURLTestHistory(tag string) *History {
  26. if s == nil {
  27. return nil
  28. }
  29. s.access.RLock()
  30. defer s.access.RUnlock()
  31. return s.delayHistory[tag]
  32. }
  33. func (s *HistoryStorage) DeleteURLTestHistory(tag string) {
  34. s.access.Lock()
  35. defer s.access.Unlock()
  36. delete(s.delayHistory, tag)
  37. }
  38. func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) {
  39. s.access.Lock()
  40. defer s.access.Unlock()
  41. s.delayHistory[tag] = history
  42. }
  43. func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
  44. if link == "" {
  45. link = "https://www.gstatic.com/generate_204"
  46. }
  47. linkURL, err := url.Parse(link)
  48. if err != nil {
  49. return
  50. }
  51. hostname := linkURL.Hostname()
  52. port := linkURL.Port()
  53. if port == "" {
  54. switch linkURL.Scheme {
  55. case "http":
  56. port = "80"
  57. case "https":
  58. port = "443"
  59. }
  60. }
  61. start := time.Now()
  62. instance, err := detour.DialContext(ctx, "tcp", M.ParseSocksaddrHostPortStr(hostname, port))
  63. if err != nil {
  64. return
  65. }
  66. defer instance.Close()
  67. req, err := http.NewRequest(http.MethodHead, link, nil)
  68. if err != nil {
  69. return
  70. }
  71. req = req.WithContext(ctx)
  72. transport := &http.Transport{
  73. Dial: func(string, string) (net.Conn, error) {
  74. return instance, nil
  75. },
  76. // from http.DefaultTransport
  77. MaxIdleConns: 100,
  78. IdleConnTimeout: 90 * time.Second,
  79. TLSHandshakeTimeout: 10 * time.Second,
  80. ExpectContinueTimeout: 1 * time.Second,
  81. }
  82. client := http.Client{
  83. Transport: transport,
  84. CheckRedirect: func(req *http.Request, via []*http.Request) error {
  85. return http.ErrUseLastResponse
  86. },
  87. }
  88. defer client.CloseIdleConnections()
  89. resp, err := client.Do(req)
  90. if err != nil {
  91. return
  92. }
  93. resp.Body.Close()
  94. t = uint16(time.Since(start) / time.Millisecond)
  95. return
  96. }