Feature: add delay history and improve url-test behavior
This commit is contained in:
parent
a57930fc3b
commit
6978963270
5 changed files with 151 additions and 7 deletions
|
@ -6,6 +6,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/queue"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,6 +33,7 @@ func (b *Base) MarshalJSON() ([]byte, error) {
|
||||||
|
|
||||||
type Proxy struct {
|
type Proxy struct {
|
||||||
C.ProxyAdapter
|
C.ProxyAdapter
|
||||||
|
history *queue.Queue
|
||||||
alive bool
|
alive bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,8 +47,54 @@ func (p *Proxy) Dial(metadata *C.Metadata) (net.Conn, error) {
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) LastDelay() (delay uint16) {
|
||||||
|
head := p.history.First()
|
||||||
|
if head == nil {
|
||||||
|
delay--
|
||||||
|
return
|
||||||
|
}
|
||||||
|
history := head.(C.DelayHistory)
|
||||||
|
if history.Delay == 0 {
|
||||||
|
delay--
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return history.Delay
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
return json.Marshal(mapping)
|
||||||
|
}
|
||||||
|
|
||||||
// URLTest get the delay for the specified URL
|
// URLTest get the delay for the specified URL
|
||||||
func (p *Proxy) URLTest(url string) (t int16, err error) {
|
func (p *Proxy) URLTest(url string) (t uint16, err error) {
|
||||||
|
defer func() {
|
||||||
|
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)
|
addr, err := urlToMetadata(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -74,10 +122,10 @@ func (p *Proxy) URLTest(url string) (t int16, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
t = int16(time.Since(start) / time.Millisecond)
|
t = uint16(time.Since(start) / time.Millisecond)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxy(adapter C.ProxyAdapter) *Proxy {
|
func NewProxy(adapter C.ProxyAdapter) *Proxy {
|
||||||
return &Proxy{adapter, true}
|
return &Proxy{adapter, queue.New(10), true}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ func (u *URLTest) Now() string {
|
||||||
func (u *URLTest) Dial(metadata *C.Metadata) (net.Conn, error) {
|
func (u *URLTest) Dial(metadata *C.Metadata) (net.Conn, error) {
|
||||||
a, err := u.fast.Dial(metadata)
|
a, err := u.fast.Dial(metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
go u.speedTest()
|
u.fallback()
|
||||||
}
|
}
|
||||||
return a, err
|
return a, err
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,23 @@ Loop:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *URLTest) fallback() {
|
||||||
|
fast := u.proxies[0]
|
||||||
|
min := fast.LastDelay()
|
||||||
|
for _, proxy := range u.proxies[1:] {
|
||||||
|
if !proxy.Alive() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
delay := proxy.LastDelay()
|
||||||
|
if delay < min {
|
||||||
|
fast = proxy
|
||||||
|
min = delay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u.fast = fast
|
||||||
|
}
|
||||||
|
|
||||||
func (u *URLTest) speedTest() {
|
func (u *URLTest) speedTest() {
|
||||||
if atomic.AddInt32(&u.once, 1) != 1 {
|
if atomic.AddInt32(&u.once, 1) != 1 {
|
||||||
return
|
return
|
||||||
|
|
71
common/queue/queue.go
Normal file
71
common/queue/queue.go
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
package queue
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Queue is a simple concurrent safe queue
|
||||||
|
type Queue struct {
|
||||||
|
items []interface{}
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put add the item to the queue.
|
||||||
|
func (q *Queue) Put(items ...interface{}) {
|
||||||
|
if len(items) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
q.lock.Lock()
|
||||||
|
q.items = append(q.items, items...)
|
||||||
|
q.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop returns the head of items.
|
||||||
|
func (q *Queue) Pop() interface{} {
|
||||||
|
if len(q.items) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
q.lock.Lock()
|
||||||
|
head := q.items[0]
|
||||||
|
q.items = q.items[1:]
|
||||||
|
q.lock.Unlock()
|
||||||
|
return head
|
||||||
|
}
|
||||||
|
|
||||||
|
// First returns the head of items without deleting.
|
||||||
|
func (q *Queue) First() interface{} {
|
||||||
|
if len(q.items) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
q.lock.RLock()
|
||||||
|
head := q.items[0]
|
||||||
|
q.lock.RUnlock()
|
||||||
|
return head
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy get the copy of queue.
|
||||||
|
func (q *Queue) Copy() []interface{} {
|
||||||
|
items := []interface{}{}
|
||||||
|
q.lock.RLock()
|
||||||
|
items = append(items, q.items...)
|
||||||
|
q.lock.RUnlock()
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of items in this queue.
|
||||||
|
func (q *Queue) Len() int64 {
|
||||||
|
q.lock.Lock()
|
||||||
|
defer q.lock.Unlock()
|
||||||
|
|
||||||
|
return int64(len(q.items))
|
||||||
|
}
|
||||||
|
|
||||||
|
// New is a constructor for a new concurrent safe queue.
|
||||||
|
func New(hint int64) *Queue {
|
||||||
|
return &Queue{
|
||||||
|
items: make([]interface{}, 0, hint),
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package constant
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Adapter Type
|
// Adapter Type
|
||||||
|
@ -31,10 +32,17 @@ type ProxyAdapter interface {
|
||||||
MarshalJSON() ([]byte, error)
|
MarshalJSON() ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DelayHistory struct {
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
Delay uint16 `json:"delay"`
|
||||||
|
}
|
||||||
|
|
||||||
type Proxy interface {
|
type Proxy interface {
|
||||||
ProxyAdapter
|
ProxyAdapter
|
||||||
Alive() bool
|
Alive() bool
|
||||||
URLTest(url string) (int16, error)
|
DelayHistory() []DelayHistory
|
||||||
|
LastDelay() uint16
|
||||||
|
URLTest(url string) (uint16, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdapterType is enum of adapter type
|
// AdapterType is enum of adapter type
|
||||||
|
|
|
@ -110,7 +110,7 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
|
proxy := r.Context().Value(CtxKeyProxy).(C.Proxy)
|
||||||
|
|
||||||
sigCh := make(chan int16)
|
sigCh := make(chan uint16)
|
||||||
go func() {
|
go func() {
|
||||||
t, err := proxy.URLTest(url)
|
t, err := proxy.URLTest(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in a new issue