mihomo/adapter/adapter.go

178 lines
3.6 KiB
Go
Raw Normal View History

2021-06-10 14:05:56 +08:00
package adapter
2018-12-22 23:56:42 +08:00
import (
2019-07-02 19:18:03 +08:00
"context"
2018-12-22 23:56:42 +08:00
"encoding/json"
2021-06-10 14:05:56 +08:00
"fmt"
"net"
"net/http"
2021-06-10 14:05:56 +08:00
"net/url"
"time"
2018-12-22 23:56:42 +08:00
"github.com/Dreamacro/clash/common/queue"
2018-12-22 23:56:42 +08:00
C "github.com/Dreamacro/clash/constant"
"go.uber.org/atomic"
2018-12-22 23:56:42 +08:00
)
type Proxy struct {
C.ProxyAdapter
history *queue.Queue
alive *atomic.Bool
}
2021-04-29 11:23:14 +08:00
// Alive implements C.Proxy
func (p *Proxy) Alive() bool {
return p.alive.Load()
}
2021-04-29 11:23:14 +08:00
// Dial implements C.Proxy
func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
2021-06-10 14:05:56 +08:00
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
defer cancel()
return p.DialContext(ctx, metadata)
}
2021-04-29 11:23:14 +08:00
// DialContext implements C.ProxyAdapter
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
conn, err := p.ProxyAdapter.DialContext(ctx, metadata)
if err != nil {
p.alive.Store(false)
}
return conn, err
}
2021-04-29 11:23:14 +08:00
// DelayHistory implements C.Proxy
func (p *Proxy) DelayHistory() []C.DelayHistory {
queue := p.history.Copy()
histories := []C.DelayHistory{}
for _, item := range queue {
histories = append(histories, item.(C.DelayHistory))
}
return histories
}
2019-09-07 16:23:43 +08:00
// LastDelay return last history record. if proxy is not alive, return the max value of uint16.
2021-04-29 11:23:14 +08:00
// implements C.Proxy
func (p *Proxy) LastDelay() (delay uint16) {
2019-03-23 16:29:27 +08:00
var max uint16 = 0xffff
if !p.alive.Load() {
2019-03-23 16:29:27 +08:00
return max
}
2019-09-26 10:08:50 +08:00
last := p.history.Last()
if last == nil {
2019-03-23 16:29:27 +08:00
return max
}
2019-09-26 10:08:50 +08:00
history := last.(C.DelayHistory)
if history.Delay == 0 {
2019-03-23 16:29:27 +08:00
return max
}
return history.Delay
}
2021-04-29 11:23:14 +08:00
// MarshalJSON implements C.ProxyAdapter
func (p *Proxy) MarshalJSON() ([]byte, error) {
inner, err := p.ProxyAdapter.MarshalJSON()
if err != nil {
return inner, err
}
mapping := map[string]interface{}{}
json.Unmarshal(inner, &mapping)
mapping["history"] = p.DelayHistory()
2019-12-13 00:29:24 +08:00
mapping["name"] = p.Name()
return json.Marshal(mapping)
}
// URLTest get the delay for the specified URL
2021-04-29 11:23:14 +08:00
// implements C.Proxy
2019-07-02 19:18:03 +08:00
func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
defer func() {
p.alive.Store(err == nil)
record := C.DelayHistory{Time: time.Now()}
if err == nil {
record.Delay = t
}
p.history.Put(record)
if p.history.Len() > 10 {
p.history.Pop()
}
}()
addr, err := urlToMetadata(url)
if err != nil {
return
}
start := time.Now()
instance, err := p.DialContext(ctx, &addr)
if err != nil {
return
}
defer instance.Close()
2019-07-02 19:18:03 +08:00
req, err := http.NewRequest(http.MethodHead, url, nil)
2019-07-02 19:18:03 +08:00
if err != nil {
return
}
req = req.WithContext(ctx)
transport := &http.Transport{
Dial: func(string, string) (net.Conn, error) {
return instance, nil
},
// from http.DefaultTransport
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
2019-07-02 19:18:03 +08:00
client := http.Client{
Transport: transport,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
2019-07-02 19:18:03 +08:00
resp, err := client.Do(req)
if err != nil {
return
}
resp.Body.Close()
t = uint16(time.Since(start) / time.Millisecond)
return
}
func NewProxy(adapter C.ProxyAdapter) *Proxy {
return &Proxy{adapter, queue.New(10), atomic.NewBool(true)}
}
2021-06-10 14:05:56 +08:00
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
}
}
addr = C.Metadata{
AddrType: C.AtypDomainName,
Host: u.Hostname(),
DstIP: nil,
DstPort: port,
}
return
}