diff --git a/adapters/remote/urltest.go b/adapters/remote/urltest.go index 00e8778..3a0fa66 100644 --- a/adapters/remote/urltest.go +++ b/adapters/remote/urltest.go @@ -1,10 +1,6 @@ package adapters import ( - "fmt" - "net" - "net/http" - "net/url" "sync" "time" @@ -14,9 +10,7 @@ import ( type URLTest struct { name string proxies []C.Proxy - url *url.URL rawURL string - addr *C.Addr fast C.Proxy delay time.Duration done chan struct{} @@ -65,7 +59,7 @@ func (u *URLTest) speedTest() { for _, p := range u.proxies { go func(p C.Proxy) { - err := getUrl(p, u.addr, u.rawURL) + _, err := DelayTest(p, u.rawURL) if err == nil { c <- p } @@ -89,76 +83,16 @@ func (u *URLTest) speedTest() { } } -func getUrl(proxy C.Proxy, addr *C.Addr, rawURL string) (err error) { - instance, err := proxy.Generator(addr) - if err != nil { - return - } - defer instance.Close() - transport := &http.Transport{ - Dial: func(string, string) (net.Conn, error) { - return instance.Conn(), nil - }, - // from http.DefaultTransport - MaxIdleConns: 100, - IdleConnTimeout: 90 * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, - } - client := http.Client{Transport: transport} - req, err := client.Get(rawURL) - if err != nil { - return - } - req.Body.Close() - return nil -} - -func selectFast(in chan interface{}) chan interface{} { - out := make(chan interface{}) - go func() { - p, open := <-in - if open { - out <- p - } - close(out) - for range in { - } - }() - - return out -} - func NewURLTest(name string, proxies []C.Proxy, rawURL string, delay time.Duration) (*URLTest, error) { - u, err := url.Parse(rawURL) + _, err := urlToAddr(rawURL) if err != nil { return nil, err } - port := u.Port() - if port == "" { - if u.Scheme == "https" { - port = "443" - } else if u.Scheme == "http" { - port = "80" - } else { - return nil, fmt.Errorf("%s scheme not Support", rawURL) - } - } - - addr := &C.Addr{ - AddrType: C.AtypDomainName, - Host: u.Hostname(), - IP: nil, - Port: port, - } - urlTest := &URLTest{ name: name, proxies: proxies[:], rawURL: rawURL, - url: u, - addr: addr, fast: proxies[0], delay: delay, done: make(chan struct{}), diff --git a/adapters/remote/util.go b/adapters/remote/util.go new file mode 100644 index 0000000..02c084b --- /dev/null +++ b/adapters/remote/util.go @@ -0,0 +1,86 @@ +package adapters + +import ( + "fmt" + "net" + "net/http" + "net/url" + "time" + + C "github.com/Dreamacro/clash/constant" +) + +// DelayTest get the delay for the specified URL +func DelayTest(proxy C.Proxy, url string) (t int16, err error) { + addr, err := urlToAddr(url) + if err != nil { + return + } + + start := time.Now() + instance, err := proxy.Generator(&addr) + if err != nil { + return + } + defer instance.Close() + transport := &http.Transport{ + Dial: func(string, string) (net.Conn, error) { + return instance.Conn(), nil + }, + // from http.DefaultTransport + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + client := http.Client{Transport: transport} + req, err := client.Get(url) + if err != nil { + return + } + req.Body.Close() + t = int16(time.Since(start) / time.Millisecond) + return +} + +func urlToAddr(rawURL string) (addr C.Addr, err error) { + u, err := url.Parse(rawURL) + if err != nil { + return + } + + port := u.Port() + if port == "" { + if u.Scheme == "https" { + port = "443" + } else if u.Scheme == "http" { + port = "80" + } else { + err = fmt.Errorf("%s scheme not Support", rawURL) + return + } + } + + addr = C.Addr{ + AddrType: C.AtypDomainName, + Host: u.Hostname(), + IP: nil, + Port: port, + } + return +} + +func selectFast(in chan interface{}) chan interface{} { + out := make(chan interface{}) + go func() { + p, open := <-in + if open { + out <- p + } + close(out) + for range in { + } + }() + + return out +} diff --git a/hub/proxies.go b/hub/proxies.go index 42c1af4..10a8e8c 100644 --- a/hub/proxies.go +++ b/hub/proxies.go @@ -3,6 +3,8 @@ package hub import ( "fmt" "net/http" + "strconv" + "time" A "github.com/Dreamacro/clash/adapters/remote" C "github.com/Dreamacro/clash/constant" @@ -15,6 +17,7 @@ func proxyRouter() http.Handler { r := chi.NewRouter() r.Get("/", getProxies) r.Get("/{name}", getProxy) + r.Get("/{name}/delay", getProxyDelay) r.Put("/{name}", updateProxy) return r } @@ -127,3 +130,64 @@ func updateProxy(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } + +type GetProxyDelayRequest struct { + URL string `json:"url"` + Timeout int16 `json:"timeout"` +} + +type GetProxyDelayResponse struct { + Delay int16 `json:"delay"` +} + +func getProxyDelay(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query() + url := query.Get("url") + timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + render.JSON(w, r, Error{ + Error: "Format error", + }) + return + } + + name := chi.URLParam(r, "name") + proxies := cfg.Proxies() + proxy, exist := proxies[name] + if !exist { + w.WriteHeader(http.StatusNotFound) + render.JSON(w, r, Error{ + Error: "Proxy not found", + }) + return + } + + sigCh := make(chan int16) + go func() { + t, err := A.DelayTest(proxy, url) + if err != nil { + sigCh <- 0 + } + sigCh <- t + }() + + select { + case <-time.After(time.Millisecond * time.Duration(timeout)): + w.WriteHeader(http.StatusRequestTimeout) + render.JSON(w, r, Error{ + Error: "Proxy delay test timeout", + }) + case t := <-sigCh: + if t == 0 { + w.WriteHeader(http.StatusServiceUnavailable) + render.JSON(w, r, Error{ + Error: "An error occurred in the delay test", + }) + } else { + render.JSON(w, r, GetProxyDelayResponse{ + Delay: t, + }) + } + } +}