From af5bd0f65ef435a003fd2d6c3ee1a28ba602cc96 Mon Sep 17 00:00:00 2001 From: yaling888 <73897884+yaling888@users.noreply.github.com> Date: Fri, 3 Jun 2022 05:00:13 +0800 Subject: [PATCH] Feature: add custom request header to proxy provider `header` value is type of string array header: Accept: - 'application/vnd.github.v3.raw' Authorization: - ' token xxxxxxxxxxx' User-Agent: - 'Clash/v1.10.6' `prefix-name` add a prefix to proxy name prefix-name: 'XXX-' --- adapter/provider/parser.go | 20 +++++++++++--------- adapter/provider/provider.go | 11 ++++++++--- adapter/provider/vehicle.go | 32 +++++++++++++++++++++++++------- config/initial.go | 18 ++++++++++++++++-- hub/route/configsGeo.go | 4 ++-- 5 files changed, 62 insertions(+), 23 deletions(-) diff --git a/adapter/provider/parser.go b/adapter/provider/parser.go index 3833dc7e..81c48f2f 100644 --- a/adapter/provider/parser.go +++ b/adapter/provider/parser.go @@ -20,13 +20,15 @@ type healthCheckSchema struct { } type proxyProviderSchema struct { - Type string `provider:"type"` - Path string `provider:"path"` - URL string `provider:"url,omitempty"` - Interval int `provider:"interval,omitempty"` - Filter string `provider:"filter,omitempty"` - HealthCheck healthCheckSchema `provider:"health-check,omitempty"` - ForceCertVerify bool `provider:"force-cert-verify,omitempty"` + Type string `provider:"type"` + Path string `provider:"path"` + URL string `provider:"url,omitempty"` + Interval int `provider:"interval,omitempty"` + Filter string `provider:"filter,omitempty"` + HealthCheck healthCheckSchema `provider:"health-check,omitempty"` + ForceCertVerify bool `provider:"force-cert-verify,omitempty"` + PrefixName string `provider:"prefix-name,omitempty"` + Header map[string][]string `provider:"header,omitempty"` } func ParseProxyProvider(name string, mapping map[string]any, forceCertVerify bool) (types.ProxyProvider, error) { @@ -59,12 +61,12 @@ func ParseProxyProvider(name string, mapping map[string]any, forceCertVerify boo case "file": vehicle = NewFileVehicle(path) case "http": - vehicle = NewHTTPVehicle(schema.URL, path) + vehicle = NewHTTPVehicle(schema.URL, path, schema.Header) default: return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type) } interval := time.Duration(uint(schema.Interval)) * time.Second filter := schema.Filter - return NewProxySetProvider(name, interval, filter, vehicle, hc, schema.ForceCertVerify) + return NewProxySetProvider(name, interval, filter, vehicle, hc, schema.ForceCertVerify, schema.PrefixName) } diff --git a/adapter/provider/provider.go b/adapter/provider/provider.go index 07dc8a89..5e6e6fab 100644 --- a/adapter/provider/provider.go +++ b/adapter/provider/provider.go @@ -97,7 +97,7 @@ func stopProxyProvider(pd *ProxySetProvider) { _ = pd.fetcher.Destroy() } -func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck, forceCertVerify bool) (*ProxySetProvider, error) { +func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck, forceCertVerify bool, prefixName string) (*ProxySetProvider, error) { filterReg, err := regexp.Compile(filter) if err != nil { return nil, fmt.Errorf("invalid filter regex: %w", err) @@ -112,7 +112,7 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh healthCheck: hc, } - fetcher := newFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, filterReg, forceCertVerify), proxiesOnUpdate(pd)) + fetcher := newFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, filterReg, forceCertVerify, prefixName), proxiesOnUpdate(pd)) pd.fetcher = fetcher wrapper := &ProxySetProvider{pd} @@ -203,7 +203,7 @@ func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) { } } -func proxiesParseAndFilter(filter string, filterReg *regexp.Regexp, forceCertVerify bool) parser[[]C.Proxy] { +func proxiesParseAndFilter(filter string, filterReg *regexp.Regexp, forceCertVerify bool, prefixName string) parser[[]C.Proxy] { return func(buf []byte) ([]C.Proxy, error) { schema := &ProxySchema{} @@ -224,6 +224,11 @@ func proxiesParseAndFilter(filter string, filterReg *regexp.Regexp, forceCertVer if name, ok := mapping["name"]; ok && len(filter) > 0 && !filterReg.MatchString(name.(string)) { continue } + + if prefixName != "" { + mapping["name"] = prefixName + mapping["name"].(string) + } + proxy, err := adapter.ParseProxy(mapping, forceCertVerify) if err != nil { return nil, fmt.Errorf("proxy %d error: %w", idx, err) diff --git a/adapter/provider/vehicle.go b/adapter/provider/vehicle.go index 4f08c317..224e6f4f 100644 --- a/adapter/provider/vehicle.go +++ b/adapter/provider/vehicle.go @@ -9,7 +9,9 @@ import ( "os" "time" + "github.com/Dreamacro/clash/common/convert" "github.com/Dreamacro/clash/component/dialer" + C "github.com/Dreamacro/clash/constant" types "github.com/Dreamacro/clash/constant/provider" ) @@ -34,8 +36,9 @@ func NewFileVehicle(path string) *FileVehicle { } type HTTPVehicle struct { - url string - path string + url string + path string + header http.Header } func (h *HTTPVehicle) Type() types.VehicleType { @@ -60,11 +63,19 @@ func (h *HTTPVehicle) Read() ([]byte, error) { return nil, err } + if h.header != nil { + req.Header = h.header + } + if user := uri.User; user != nil { password, _ := user.Password() req.SetBasicAuth(user.Username(), password) } + if req.UserAgent() == "" { + convert.SetUserAgent(req) + } + req = req.WithContext(ctx) transport := &http.Transport{ @@ -73,8 +84,13 @@ func (h *HTTPVehicle) Read() ([]byte, error) { IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, - DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { - return dialer.DialContext(ctx, network, address) + DialContext: func(ctx context.Context, network, address string) (conn net.Conn, err error) { + conn, err = dialer.DialContext(ctx, network, address) // with direct + if err != nil { + // fallback to tun if tun enabled + conn, err = (&net.Dialer{Timeout: C.DefaultTCPTimeout}).Dial(network, address) + } + return }, } @@ -83,7 +99,9 @@ func (h *HTTPVehicle) Read() ([]byte, error) { if err != nil { return nil, err } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() buf, err := io.ReadAll(resp.Body) if err != nil { @@ -93,6 +111,6 @@ func (h *HTTPVehicle) Read() ([]byte, error) { return buf, nil } -func NewHTTPVehicle(url string, path string) *HTTPVehicle { - return &HTTPVehicle{url, path} +func NewHTTPVehicle(url string, path string, header http.Header) *HTTPVehicle { + return &HTTPVehicle{url, path, header} } diff --git a/config/initial.go b/config/initial.go index 2d816805..3f082905 100644 --- a/config/initial.go +++ b/config/initial.go @@ -6,13 +6,14 @@ import ( "net/http" "os" + "github.com/Dreamacro/clash/common/convert" "github.com/Dreamacro/clash/component/mmdb" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" ) func downloadMMDB(path string) (err error) { - resp, err := http.Get("https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb") + resp, err := doGet("https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb") if err != nil { return } @@ -51,7 +52,7 @@ func initMMDB() error { } func downloadGeoSite(path string) (err error) { - resp, err := http.Get("https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat") + resp, err := doGet("https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat") if err != nil { return } @@ -110,3 +111,16 @@ func Init(dir string) error { } return nil } + +func doGet(url string) (resp *http.Response, err error) { + var req *http.Request + req, err = http.NewRequest("GET", url, nil) + if err != nil { + return + } + + convert.SetUserAgent(req) + + resp, err = http.DefaultClient.Do(req) + return +} diff --git a/hub/route/configsGeo.go b/hub/route/configsGeo.go index a38ab03a..f64995ed 100644 --- a/hub/route/configsGeo.go +++ b/hub/route/configsGeo.go @@ -49,14 +49,14 @@ func updateGeoDatabases(w http.ResponseWriter, r *http.Request) { return } + log.Warnln("[REST-API] update GEO databases successful, apply config...") + cfg, err := executor.ParseWithPath(constant.Path.Config()) if err != nil { log.Errorln("[REST-API] update GEO databases failed: %v", err) return } - log.Warnln("[REST-API] update GEO databases successful, apply config...") - executor.ApplyConfig(cfg, false) }()