return expected status through Rest API and clean useless code
This commit is contained in:
parent
3b57a923fd
commit
cc6429722a
10 changed files with 68 additions and 91 deletions
|
@ -17,8 +17,6 @@ import (
|
||||||
"github.com/metacubex/mihomo/common/utils"
|
"github.com/metacubex/mihomo/common/utils"
|
||||||
"github.com/metacubex/mihomo/component/dialer"
|
"github.com/metacubex/mihomo/component/dialer"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
"github.com/metacubex/mihomo/log"
|
|
||||||
|
|
||||||
"github.com/puzpuzpuz/xsync/v3"
|
"github.com/puzpuzpuz/xsync/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -41,11 +39,6 @@ type Proxy struct {
|
||||||
extra *xsync.MapOf[string, *extraProxyState]
|
extra *xsync.MapOf[string, *extraProxyState]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alive implements C.Proxy
|
|
||||||
func (p *Proxy) Alive() bool {
|
|
||||||
return p.alive.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
// AliveForTestUrl implements C.Proxy
|
// AliveForTestUrl implements C.Proxy
|
||||||
func (p *Proxy) AliveForTestUrl(url string) bool {
|
func (p *Proxy) AliveForTestUrl(url string) bool {
|
||||||
if state, ok := p.extra.Load(url); ok {
|
if state, ok := p.extra.Load(url); ok {
|
||||||
|
@ -181,7 +174,7 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
|
||||||
_ = json.Unmarshal(inner, &mapping)
|
_ = json.Unmarshal(inner, &mapping)
|
||||||
mapping["history"] = p.DelayHistory()
|
mapping["history"] = p.DelayHistory()
|
||||||
mapping["extra"] = p.ExtraDelayHistory()
|
mapping["extra"] = p.ExtraDelayHistory()
|
||||||
mapping["alive"] = p.Alive()
|
mapping["alive"] = p.AliveForTestUrl(p.url)
|
||||||
mapping["name"] = p.Name()
|
mapping["name"] = p.Name()
|
||||||
mapping["udp"] = p.SupportUDP()
|
mapping["udp"] = p.SupportUDP()
|
||||||
mapping["xudp"] = p.SupportXUDP()
|
mapping["xudp"] = p.SupportXUDP()
|
||||||
|
@ -191,13 +184,11 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
|
||||||
|
|
||||||
// URLTest get the delay for the specified URL
|
// URLTest get the delay for the specified URL
|
||||||
// implements C.Proxy
|
// implements C.Proxy
|
||||||
func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16], store C.DelayHistoryStoreType) (t uint16, err error) {
|
func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (t uint16, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
alive := err == nil
|
alive := err == nil
|
||||||
store = p.determineFinalStoreType(store, url)
|
|
||||||
|
|
||||||
switch store {
|
if len(p.url) == 0 || url == p.url {
|
||||||
case C.OriginalHistory:
|
|
||||||
p.alive.Store(alive)
|
p.alive.Store(alive)
|
||||||
record := C.DelayHistory{Time: time.Now()}
|
record := C.DelayHistory{Time: time.Now()}
|
||||||
if alive {
|
if alive {
|
||||||
|
@ -212,7 +203,7 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
|
||||||
if len(p.url) == 0 {
|
if len(p.url) == 0 {
|
||||||
p.url = url
|
p.url = url
|
||||||
}
|
}
|
||||||
case C.ExtraHistory:
|
} else {
|
||||||
record := C.DelayHistory{Time: time.Now()}
|
record := C.DelayHistory{Time: time.Now()}
|
||||||
if alive {
|
if alive {
|
||||||
record.Delay = t
|
record.Delay = t
|
||||||
|
@ -236,8 +227,6 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
|
||||||
if state.history.Len() > defaultHistoriesNum {
|
if state.history.Len() > defaultHistoriesNum {
|
||||||
state.history.Pop()
|
state.history.Pop()
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
log.Debugln("health check result will be discarded, url: %s alive: %t, delay: %d", url, alive, t)
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -349,24 +338,3 @@ func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Proxy) determineFinalStoreType(store C.DelayHistoryStoreType, url string) C.DelayHistoryStoreType {
|
|
||||||
if store != C.DropHistory {
|
|
||||||
return store
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(p.url) == 0 || url == p.url {
|
|
||||||
return C.OriginalHistory
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.extra.Size() < 2*C.DefaultMaxHealthCheckUrlNum {
|
|
||||||
return C.ExtraHistory
|
|
||||||
}
|
|
||||||
|
|
||||||
_, ok := p.extra.Load(url)
|
|
||||||
if ok {
|
|
||||||
return C.ExtraHistory
|
|
||||||
}
|
|
||||||
|
|
||||||
return store
|
|
||||||
}
|
|
||||||
|
|
|
@ -88,7 +88,7 @@ func (f *Fallback) MarshalJSON() ([]byte, error) {
|
||||||
"now": f.Now(),
|
"now": f.Now(),
|
||||||
"all": all,
|
"all": all,
|
||||||
"testUrl": f.testUrl,
|
"testUrl": f.testUrl,
|
||||||
"expected": f.expectedStatus,
|
"expectedStatus": f.expectedStatus,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,13 +102,11 @@ func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
|
||||||
proxies := f.GetProxies(touch)
|
proxies := f.GetProxies(touch)
|
||||||
for _, proxy := range proxies {
|
for _, proxy := range proxies {
|
||||||
if len(f.selected) == 0 {
|
if len(f.selected) == 0 {
|
||||||
// if proxy.Alive() {
|
|
||||||
if proxy.AliveForTestUrl(f.testUrl) {
|
if proxy.AliveForTestUrl(f.testUrl) {
|
||||||
return proxy
|
return proxy
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if proxy.Name() == f.selected {
|
if proxy.Name() == f.selected {
|
||||||
// if proxy.Alive() {
|
|
||||||
if proxy.AliveForTestUrl(f.testUrl) {
|
if proxy.AliveForTestUrl(f.testUrl) {
|
||||||
return proxy
|
return proxy
|
||||||
} else {
|
} else {
|
||||||
|
@ -135,12 +133,11 @@ func (f *Fallback) Set(name string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
f.selected = name
|
f.selected = name
|
||||||
// if !p.Alive() {
|
|
||||||
if !p.AliveForTestUrl(f.testUrl) {
|
if !p.AliveForTestUrl(f.testUrl) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000))
|
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000))
|
||||||
defer cancel()
|
defer cancel()
|
||||||
expectedStatus, _ := utils.NewIntRanges[uint16](f.expectedStatus)
|
expectedStatus, _ := utils.NewIntRanges[uint16](f.expectedStatus)
|
||||||
_, _ = p.URLTest(ctx, f.testUrl, expectedStatus, C.ExtraHistory)
|
_, _ = p.URLTest(ctx, f.testUrl, expectedStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -202,7 +202,7 @@ func (gb *GroupBase) URLTest(ctx context.Context, url string, expectedStatus uti
|
||||||
proxy := proxy
|
proxy := proxy
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
delay, err := proxy.URLTest(ctx, url, expectedStatus, C.DropHistory)
|
delay, err := proxy.URLTest(ctx, url, expectedStatus)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
mp[proxy.Name()] = delay
|
mp[proxy.Name()] = delay
|
||||||
|
|
|
@ -95,7 +95,8 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||||
// select don't need health check
|
// select don't need health check
|
||||||
if groupOption.Type != "select" && groupOption.Type != "relay" {
|
if groupOption.Type != "select" && groupOption.Type != "relay" {
|
||||||
if groupOption.URL == "" {
|
if groupOption.URL == "" {
|
||||||
groupOption.URL = "https://cp.cloudflare.com/generate_204"
|
groupOption.URL = C.DefaultTestURL
|
||||||
|
testUrl = groupOption.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
if groupOption.Interval == 0 {
|
if groupOption.Interval == 0 {
|
||||||
|
|
|
@ -101,7 +101,7 @@ func (u *URLTest) fast(touch bool) C.Proxy {
|
||||||
proxies := u.GetProxies(touch)
|
proxies := u.GetProxies(touch)
|
||||||
if u.selected != "" {
|
if u.selected != "" {
|
||||||
for _, proxy := range proxies {
|
for _, proxy := range proxies {
|
||||||
if !proxy.Alive() {
|
if !proxy.AliveForTestUrl(u.testUrl) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if proxy.Name() == u.selected {
|
if proxy.Name() == u.selected {
|
||||||
|
@ -113,7 +113,6 @@ func (u *URLTest) fast(touch bool) C.Proxy {
|
||||||
|
|
||||||
elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) {
|
elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) {
|
||||||
fast := proxies[0]
|
fast := proxies[0]
|
||||||
// min := fast.LastDelay()
|
|
||||||
min := fast.LastDelayForTestUrl(u.testUrl)
|
min := fast.LastDelayForTestUrl(u.testUrl)
|
||||||
fastNotExist := true
|
fastNotExist := true
|
||||||
|
|
||||||
|
@ -122,12 +121,10 @@ func (u *URLTest) fast(touch bool) C.Proxy {
|
||||||
fastNotExist = false
|
fastNotExist = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// if !proxy.Alive() {
|
|
||||||
if !proxy.AliveForTestUrl(u.testUrl) {
|
if !proxy.AliveForTestUrl(u.testUrl) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// delay := proxy.LastDelay()
|
|
||||||
delay := proxy.LastDelayForTestUrl(u.testUrl)
|
delay := proxy.LastDelayForTestUrl(u.testUrl)
|
||||||
if delay < min {
|
if delay < min {
|
||||||
fast = proxy
|
fast = proxy
|
||||||
|
@ -136,7 +133,6 @@ func (u *URLTest) fast(touch bool) C.Proxy {
|
||||||
|
|
||||||
}
|
}
|
||||||
// tolerance
|
// tolerance
|
||||||
// if u.fastNode == nil || fastNotExist || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
|
|
||||||
if u.fastNode == nil || fastNotExist || !u.fastNode.AliveForTestUrl(u.testUrl) || u.fastNode.LastDelayForTestUrl(u.testUrl) > fast.LastDelayForTestUrl(u.testUrl)+u.tolerance {
|
if u.fastNode == nil || fastNotExist || !u.fastNode.AliveForTestUrl(u.testUrl) || u.fastNode.LastDelayForTestUrl(u.testUrl) > fast.LastDelayForTestUrl(u.testUrl)+u.tolerance {
|
||||||
u.fastNode = fast
|
u.fastNode = fast
|
||||||
}
|
}
|
||||||
|
@ -173,7 +169,7 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
|
||||||
"now": u.Now(),
|
"now": u.Now(),
|
||||||
"all": all,
|
"all": all,
|
||||||
"testUrl": u.testUrl,
|
"testUrl": u.testUrl,
|
||||||
"expected": u.expectedStatus,
|
"expectedStatus": u.expectedStatus,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultURLTestTimeout = time.Second * 5
|
defaultURLTestTimeout = time.Second * 5
|
||||||
defaultURLTestURL = "https://www.gstatic.com/generate_204"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type HealthCheckOption struct {
|
type HealthCheckOption struct {
|
||||||
|
@ -105,12 +104,6 @@ func (hc *HealthCheck) registerHealthCheckTask(url string, expectedStatus utils.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// due to the time-consuming nature of health checks, a maximum of defaultMaxTestURLNum URLs can be set for testing
|
|
||||||
if len(hc.extra) > C.DefaultMaxHealthCheckUrlNum {
|
|
||||||
log.Debugln("skip add url: %s to health check because it has reached the maximum limit: %d", url, C.DefaultMaxHealthCheckUrlNum)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
option := &extraOption{filters: map[string]struct{}{}, expectedStatus: expectedStatus}
|
option := &extraOption{filters: map[string]struct{}{}, expectedStatus: expectedStatus}
|
||||||
splitAndAddFiltersToExtra(filter, option)
|
splitAndAddFiltersToExtra(filter, option)
|
||||||
hc.extra[url] = option
|
hc.extra[url] = option
|
||||||
|
@ -182,13 +175,8 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex
|
||||||
}
|
}
|
||||||
|
|
||||||
var filterReg *regexp2.Regexp
|
var filterReg *regexp2.Regexp
|
||||||
var store = C.OriginalHistory
|
|
||||||
var expectedStatus utils.IntRanges[uint16]
|
var expectedStatus utils.IntRanges[uint16]
|
||||||
if option != nil {
|
if option != nil {
|
||||||
if url != hc.url {
|
|
||||||
store = C.ExtraHistory
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedStatus = option.expectedStatus
|
expectedStatus = option.expectedStatus
|
||||||
if len(option.filters) != 0 {
|
if len(option.filters) != 0 {
|
||||||
filters := make([]string, 0, len(option.filters))
|
filters := make([]string, 0, len(option.filters))
|
||||||
|
@ -213,7 +201,7 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
log.Debugln("Health Checking, proxy: %s, url: %s, id: {%s}", p.Name(), url, uid)
|
log.Debugln("Health Checking, proxy: %s, url: %s, id: {%s}", p.Name(), url, uid)
|
||||||
_, _ = p.URLTest(ctx, url, expectedStatus, store)
|
_, _ = p.URLTest(ctx, url, expectedStatus)
|
||||||
log.Debugln("Health Checked, proxy: %s, url: %s, alive: %t, delay: %d ms uid: {%s}", p.Name(), url, p.AliveForTestUrl(url), p.LastDelayForTestUrl(url), uid)
|
log.Debugln("Health Checked, proxy: %s, url: %s, alive: %t, delay: %d ms uid: {%s}", p.Name(), url, p.AliveForTestUrl(url), p.LastDelayForTestUrl(url), uid)
|
||||||
return false, nil
|
return false, nil
|
||||||
})
|
})
|
||||||
|
@ -228,7 +216,7 @@ func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool, exp
|
||||||
if len(url) == 0 {
|
if len(url) == 0 {
|
||||||
interval = 0
|
interval = 0
|
||||||
expectedStatus = nil
|
expectedStatus = nil
|
||||||
url = defaultURLTestURL
|
url = C.DefaultTestURL
|
||||||
}
|
}
|
||||||
|
|
||||||
return &HealthCheck{
|
return &HealthCheck{
|
||||||
|
|
|
@ -48,12 +48,18 @@ type proxySetProvider struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
|
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
|
||||||
|
expectedStatus := "*"
|
||||||
|
if pp.healthCheck.expectedStatus != nil {
|
||||||
|
expectedStatus = pp.healthCheck.expectedStatus.ToString()
|
||||||
|
}
|
||||||
|
|
||||||
return json.Marshal(map[string]any{
|
return json.Marshal(map[string]any{
|
||||||
"name": pp.Name(),
|
"name": pp.Name(),
|
||||||
"type": pp.Type().String(),
|
"type": pp.Type().String(),
|
||||||
"vehicleType": pp.VehicleType().String(),
|
"vehicleType": pp.VehicleType().String(),
|
||||||
"proxies": pp.Proxies(),
|
"proxies": pp.Proxies(),
|
||||||
"testUrl": pp.healthCheck.url,
|
"testUrl": pp.healthCheck.url,
|
||||||
|
"expectedStatus": expectedStatus,
|
||||||
"updatedAt": pp.UpdatedAt,
|
"updatedAt": pp.UpdatedAt,
|
||||||
"subscriptionInfo": pp.subscriptionInfo,
|
"subscriptionInfo": pp.subscriptionInfo,
|
||||||
})
|
})
|
||||||
|
@ -214,12 +220,18 @@ type compatibleProvider struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
|
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
|
||||||
|
expectedStatus := "*"
|
||||||
|
if cp.healthCheck.expectedStatus != nil {
|
||||||
|
expectedStatus = cp.healthCheck.expectedStatus.ToString()
|
||||||
|
}
|
||||||
|
|
||||||
return json.Marshal(map[string]any{
|
return json.Marshal(map[string]any{
|
||||||
"name": cp.Name(),
|
"name": cp.Name(),
|
||||||
"type": cp.Type().String(),
|
"type": cp.Type().String(),
|
||||||
"vehicleType": cp.VehicleType().String(),
|
"vehicleType": cp.VehicleType().String(),
|
||||||
"proxies": cp.Proxies(),
|
"proxies": cp.Proxies(),
|
||||||
"testUrl": cp.healthCheck.url,
|
"testUrl": cp.healthCheck.url,
|
||||||
|
"expectedStatus": expectedStatus,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,3 +75,26 @@ func (ranges IntRanges[T]) Check(status T) bool {
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ranges IntRanges[T]) ToString() string {
|
||||||
|
if len(ranges) == 0 {
|
||||||
|
return "*"
|
||||||
|
}
|
||||||
|
|
||||||
|
terms := make([]string, len(ranges))
|
||||||
|
for i, r := range ranges {
|
||||||
|
start := r.Start()
|
||||||
|
end := r.End()
|
||||||
|
|
||||||
|
var term string
|
||||||
|
if start == end {
|
||||||
|
term = strconv.Itoa(int(start))
|
||||||
|
} else {
|
||||||
|
term = strconv.Itoa(int(start)) + "-" + strconv.Itoa(int(end))
|
||||||
|
}
|
||||||
|
|
||||||
|
terms[i] = term
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(terms, "/")
|
||||||
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ const (
|
||||||
DefaultDropTime = 12 * DefaultTCPTimeout
|
DefaultDropTime = 12 * DefaultTCPTimeout
|
||||||
DefaultUDPTimeout = DefaultTCPTimeout
|
DefaultUDPTimeout = DefaultTCPTimeout
|
||||||
DefaultTLSTimeout = DefaultTCPTimeout
|
DefaultTLSTimeout = DefaultTCPTimeout
|
||||||
DefaultMaxHealthCheckUrlNum = 16
|
DefaultTestURL = "https://cp.cloudflare.com/generate_204"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrNotSupport = errors.New("no support")
|
var ErrNotSupport = errors.New("no support")
|
||||||
|
@ -149,21 +149,13 @@ type DelayHistory struct {
|
||||||
|
|
||||||
type DelayHistoryStoreType int
|
type DelayHistoryStoreType int
|
||||||
|
|
||||||
const (
|
|
||||||
OriginalHistory DelayHistoryStoreType = iota
|
|
||||||
ExtraHistory
|
|
||||||
DropHistory
|
|
||||||
)
|
|
||||||
|
|
||||||
type Proxy interface {
|
type Proxy interface {
|
||||||
ProxyAdapter
|
ProxyAdapter
|
||||||
Alive() bool
|
|
||||||
AliveForTestUrl(url string) bool
|
AliveForTestUrl(url string) bool
|
||||||
DelayHistory() []DelayHistory
|
DelayHistory() []DelayHistory
|
||||||
ExtraDelayHistory() map[string][]DelayHistory
|
ExtraDelayHistory() map[string][]DelayHistory
|
||||||
LastDelay() uint16
|
|
||||||
LastDelayForTestUrl(url string) uint16
|
LastDelayForTestUrl(url string) uint16
|
||||||
URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16], store DelayHistoryStoreType) (uint16, error)
|
URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (uint16, error)
|
||||||
|
|
||||||
// Deprecated: use DialContext instead.
|
// Deprecated: use DialContext instead.
|
||||||
Dial(metadata *Metadata) (Conn, error)
|
Dial(metadata *Metadata) (Conn, error)
|
||||||
|
|
|
@ -125,7 +125,7 @@ func getProxyDelay(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout))
|
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout))
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
delay, err := proxy.URLTest(ctx, url, expectedStatus, C.ExtraHistory)
|
delay, err := proxy.URLTest(ctx, url, expectedStatus)
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
render.Status(r, http.StatusGatewayTimeout)
|
render.Status(r, http.StatusGatewayTimeout)
|
||||||
render.JSON(w, r, ErrRequestTimeout)
|
render.JSON(w, r, ErrRequestTimeout)
|
||||||
|
|
Loading…
Reference in a new issue