proxy-detector/pkg/health-check/health.go
2024-12-11 13:22:19 +08:00

140 lines
3.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package healthcheck
import (
"context"
"fmt"
"net"
"net/http"
"net/netip"
"net/url"
"strconv"
"sync"
"time"
"gitea.timerzz.com/timerzz/proxy-detector/config"
"gitea.timerzz.com/timerzz/proxy-detector/log"
"gitea.timerzz.com/timerzz/proxy-detector/pkg/proxy/structs"
"gitea.timerzz.com/timerzz/proxy-detector/pkg/worker"
C "github.com/metacubex/mihomo/constant"
)
// Return 0 for error
func HealthCheck(ctx context.Context, proxy C.Proxy) (err error) {
return HTTPGetViaProxy(ctx, proxy, "https://www.coachoutlet.com/api/get-shop/bags/view-all")
}
func CleanBadProxies(ctx context.Context, proxies []structs.Proxy) (newProxies []structs.Proxy) {
newProxies = make([]structs.Proxy, 0, len(proxies))
var m sync.Mutex
var wg sync.WaitGroup
for _, p := range proxies {
var proxy = p
wg.Add(1)
err := worker.Pool.Submit(func() {
defer wg.Done()
err := HealthCheck(ctx, proxy.ClashProxy())
if err == nil {
m.Lock()
newProxies = append(newProxies, proxy)
m.Unlock()
}
})
if err != nil {
log.Errorln("添加并发任务失败: ", err)
}
}
wg.Wait()
return newProxies
}
func HTTPGetViaProxy(ctx context.Context, clashProxy C.Proxy, url string) error {
ctx, cancel := context.WithTimeout(ctx, time.Duration(config.Config.HealthCheckTimeout)*time.Second)
defer cancel()
addr, err := UrlToMetadata(url)
if err != nil {
return err
}
conn, err := clashProxy.DialContext(ctx, &addr) // 建立到proxy server的connection对Proxy的类别做了自适应相当于泛型
if err != nil {
return err
}
defer conn.Close()
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return err
}
req = req.WithContext(ctx)
transport := &http.Transport{
// Note: Dial specifies the dial function for creating unencrypted TCP connections.
// When httpClient sets this transport, it will use the tcp/udp connection returned from
// function Dial instead of default tcp/udp connection. It's the key to set custom proxy for http transport
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return conn, nil
},
// from http.DefaultTransport
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
req.Header = http.Header{
"User-Agent": []string{"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0"},
"sec-fetch-site": []string{"same-origin"},
"upgrade-insecure-requests": []string{"1"},
"sec-fetch-dest": []string{"empty"},
"sec-fetch-mode": []string{"navigate"},
"Accept": []string{"*/*"},
"Accept-Encoding": []string{"gzip, deflate, br"},
"Connection": []string{"keep-alive"},
}
client := http.Client{
Transport: transport,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
resp, err := client.Do(req)
if err != nil {
return err
}
if resp.StatusCode != 200 {
return fmt.Errorf("%d %s for proxy %s %s", resp.StatusCode, resp.Status, clashProxy.Name(), clashProxy.Addr())
}
resp.Body.Close()
return nil
}
func UrlToMetadata(rawURL string) (addr C.Metadata, err error) {
u, err := url.Parse(rawURL)
if err != nil {
return
}
port := u.Port()
if port == "" {
switch u.Scheme {
case "https":
port = "443"
case "http":
port = "80"
default:
err = fmt.Errorf("%s scheme not Support", rawURL)
return
}
}
uintPort, err := strconv.ParseUint(port, 10, 16)
if err != nil {
return
}
addr = C.Metadata{
Host: u.Hostname(),
DstIP: netip.Addr{},
DstPort: uint16(uintPort),
}
return
}