From 6bfcb2e1f551620dab108b2d893bd3dfad93425a Mon Sep 17 00:00:00 2001 From: Kawin Viriyaprasopsook Date: Mon, 22 Jul 2024 15:26:58 +0700 Subject: [PATCH 1/8] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20slices.SortFunc=20for?= =?UTF-8?q?=20upstreams?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mod/utils/utils.go | 52 ++++++++++++++++++++++++------------------ src/upstreams.go | 20 ++++++++-------- 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/src/mod/utils/utils.go b/src/mod/utils/utils.go index a61d5ed..21d2e40 100644 --- a/src/mod/utils/utils.go +++ b/src/mod/utils/utils.go @@ -41,12 +41,12 @@ func SendOK(w http.ResponseWriter) { // Get GET parameter func GetPara(r *http.Request, key string) (string, error) { - keys, ok := r.URL.Query()[key] - if !ok || len(keys[0]) < 1 { + // Get first value from the URL query + value := r.URL.Query().Get(key) + if len(value) == 0 { return "", errors.New("invalid " + key + " given") - } else { - return keys[0], nil } + return value, nil } // Get GET paramter as boolean, accept 1 or true @@ -56,26 +56,29 @@ func GetBool(r *http.Request, key string) (bool, error) { return false, err } - x = strings.TrimSpace(x) - - if x == "1" || strings.ToLower(x) == "true" || strings.ToLower(x) == "on" { + // Convert to lowercase and trim spaces just once to compare + switch strings.ToLower(strings.TrimSpace(x)) { + case "1", "true", "on": return true, nil - } else if x == "0" || strings.ToLower(x) == "false" || strings.ToLower(x) == "off" { + case "0", "false", "off": return false, nil } return false, errors.New("invalid boolean given") } -// Get POST paramter +// Get POST parameter func PostPara(r *http.Request, key string) (string, error) { - r.ParseForm() - x := r.Form.Get(key) - if x == "" { - return "", errors.New("invalid " + key + " given") - } else { - return x, nil + // Try to parse the form + if err := r.ParseForm(); err != nil { + return "", err } + // Get first value from the form + x := r.Form.Get(key) + if len(x) == 0 { + return "", errors.New("invalid " + key + " given") + } + return x, nil } // Get POST paramter as boolean, accept 1 or true @@ -85,11 +88,11 @@ func PostBool(r *http.Request, key string) (bool, error) { return false, err } - x = strings.TrimSpace(x) - - if x == "1" || strings.ToLower(x) == "true" || strings.ToLower(x) == "on" { + // Convert to lowercase and trim spaces just once to compare + switch strings.ToLower(strings.TrimSpace(x)) { + case "1", "true", "on": return true, nil - } else if x == "0" || strings.ToLower(x) == "false" || strings.ToLower(x) == "off" { + case "0", "false", "off": return false, nil } @@ -114,14 +117,19 @@ func PostInt(r *http.Request, key string) (int, error) { func FileExists(filename string) bool { _, err := os.Stat(filename) - if os.IsNotExist(err) { + if err == nil { + // File exists + return true + } else if errors.Is(err, os.ErrNotExist) { + // File does not exist return false } - return true + // Some other error + return false } func IsDir(path string) bool { - if FileExists(path) == false { + if !FileExists(path) { return false } fi, err := os.Stat(path) diff --git a/src/upstreams.go b/src/upstreams.go index 3ab2eab..c05e645 100644 --- a/src/upstreams.go +++ b/src/upstreams.go @@ -1,9 +1,10 @@ package main import ( + "cmp" "encoding/json" "net/http" - "sort" + "slices" "strings" "imuslab.com/zoraxy/mod/dynamicproxy/loadbalance" @@ -33,19 +34,18 @@ func ReverseProxyUpstreamList(w http.ResponseWriter, r *http.Request) { activeUpstreams := targetEndpoint.ActiveOrigins inactiveUpstreams := targetEndpoint.InactiveOrigins - // Sort the upstreams slice by weight, then by origin domain alphabetically - sort.Slice(activeUpstreams, func(i, j int) bool { - if activeUpstreams[i].Weight != activeUpstreams[j].Weight { - return activeUpstreams[i].Weight > activeUpstreams[j].Weight + slices.SortFunc(activeUpstreams, func(i, j *loadbalance.Upstream) int { + if i.Weight != j.Weight { + return cmp.Compare(j.Weight, i.Weight) } - return activeUpstreams[i].OriginIpOrDomain < activeUpstreams[j].OriginIpOrDomain + return cmp.Compare(i.OriginIpOrDomain, j.OriginIpOrDomain) }) - sort.Slice(inactiveUpstreams, func(i, j int) bool { - if inactiveUpstreams[i].Weight != inactiveUpstreams[j].Weight { - return inactiveUpstreams[i].Weight > inactiveUpstreams[j].Weight + slices.SortFunc(inactiveUpstreams, func(i, j *loadbalance.Upstream) int { + if i.Weight != j.Weight { + return cmp.Compare(j.Weight, i.Weight) } - return inactiveUpstreams[i].OriginIpOrDomain < inactiveUpstreams[j].OriginIpOrDomain + return cmp.Compare(i.OriginIpOrDomain, j.OriginIpOrDomain) }) type UpstreamCombinedList struct { From 0dddd1f9e3df0f81f34818e21cb9fd0cabd178f7 Mon Sep 17 00:00:00 2001 From: Kawin Viriyaprasopsook Date: Mon, 22 Jul 2024 15:29:43 +0700 Subject: [PATCH 2/8] =?UTF-8?q?=F0=9F=93=9D=20discribe=20for=20upstream=20?= =?UTF-8?q?sort=20func?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/upstreams.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/upstreams.go b/src/upstreams.go index c05e645..dd53c83 100644 --- a/src/upstreams.go +++ b/src/upstreams.go @@ -36,15 +36,19 @@ func ReverseProxyUpstreamList(w http.ResponseWriter, r *http.Request) { inactiveUpstreams := targetEndpoint.InactiveOrigins slices.SortFunc(activeUpstreams, func(i, j *loadbalance.Upstream) int { if i.Weight != j.Weight { + // sort by weight DESC return cmp.Compare(j.Weight, i.Weight) } + // sort by origin ASC return cmp.Compare(i.OriginIpOrDomain, j.OriginIpOrDomain) }) slices.SortFunc(inactiveUpstreams, func(i, j *loadbalance.Upstream) int { if i.Weight != j.Weight { + // sort by weight DESC return cmp.Compare(j.Weight, i.Weight) } + // sort by origin ASC return cmp.Compare(i.OriginIpOrDomain, j.OriginIpOrDomain) }) From bec363ababa22eb4c7f9e968c862f326c50d3130 Mon Sep 17 00:00:00 2001 From: bouroo Date: Mon, 22 Jul 2024 23:39:47 +0700 Subject: [PATCH 3/8] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20immediate=20return=20i?= =?UTF-8?q?f=20single=20upstream?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dynamicproxy/loadbalance/originPicker.go | 61 +++++++++++-------- src/mod/utils/utils.go | 2 +- src/upstreams.go | 4 +- 3 files changed, 38 insertions(+), 29 deletions(-) diff --git a/src/mod/dynamicproxy/loadbalance/originPicker.go b/src/mod/dynamicproxy/loadbalance/originPicker.go index 51bcc2b..ad5ddc4 100644 --- a/src/mod/dynamicproxy/loadbalance/originPicker.go +++ b/src/mod/dynamicproxy/loadbalance/originPicker.go @@ -102,39 +102,48 @@ func (m *RouteManager) getSessionHandler(r *http.Request, upstreams []*Upstream) /* Functions related to random upstream picking */ // Get a random upstream by the weights defined in Upstream struct, return the upstream, index value and any error func getRandomUpstreamByWeight(upstreams []*Upstream) (*Upstream, int, error) { - var ret *Upstream - sum := 0 - for _, c := range upstreams { - sum += c.Weight + totalUpstreams := len(upstreams) + if totalUpstreams == 1 { + return upstreams[0], 0, nil } - r, err := intRange(0, sum) - if err != nil { - return ret, -1, err - } - counter := 0 - for _, c := range upstreams { - r -= c.Weight - if r < 0 { - return c, counter, nil - } - counter++ + if totalUpstreams == 0 { + return nil, -1, errors.New("no upstream servers available") } - if ret == nil { - //All fallback - //use the first one that is with weight = 0 - fallbackUpstreams := []*Upstream{} - fallbackUpstreamsOriginalID := []int{} - for ix, upstream := range upstreams { - if upstream.Weight == 0 { - fallbackUpstreams = append(fallbackUpstreams, upstream) - fallbackUpstreamsOriginalID = append(fallbackUpstreamsOriginalID, ix) - } + totalWeight := 0 + fallbackUpstreams := make([]*Upstream, 0) // List of upstreams with weight 0 + fallbackUpstreamsOriginalID := make([]int, 0) + + // Calculate total weight and gather fallbacks + for ix, upstream := range upstreams { + totalWeight += upstream.Weight + if upstream.Weight == 0 { + fallbackUpstreams = append(fallbackUpstreams, upstream) + fallbackUpstreamsOriginalID = append(fallbackUpstreamsOriginalID, ix) + } + } + + if totalWeight == 0 { + // If total weight is 0, fallback to a random upstream with weight 0 + if len(fallbackUpstreams) == 0 { + return nil, -1, errors.New("no valid upstream servers available") } upstreamID := rand.Intn(len(fallbackUpstreams)) return fallbackUpstreams[upstreamID], fallbackUpstreamsOriginalID[upstreamID], nil } - return ret, -1, errors.New("failed to pick an upstream origin server") + + // Generate a random number in the range of total weight + r := rand.Intn(totalWeight) + + // Select upstream based on random number + for i, upstream := range upstreams { + r -= upstream.Weight + if r < 0 { + return upstream, i, nil + } + } + + return nil, -1, errors.New("failed to pick an upstream origin server") } // IntRange returns a random integer in the range from min to max. diff --git a/src/mod/utils/utils.go b/src/mod/utils/utils.go index 21d2e40..2fe1ffd 100644 --- a/src/mod/utils/utils.go +++ b/src/mod/utils/utils.go @@ -199,4 +199,4 @@ func ValidateListeningAddress(address string) bool { } return true -} +} \ No newline at end of file diff --git a/src/upstreams.go b/src/upstreams.go index dd53c83..8241f04 100644 --- a/src/upstreams.go +++ b/src/upstreams.go @@ -34,7 +34,7 @@ func ReverseProxyUpstreamList(w http.ResponseWriter, r *http.Request) { activeUpstreams := targetEndpoint.ActiveOrigins inactiveUpstreams := targetEndpoint.InactiveOrigins - slices.SortFunc(activeUpstreams, func(i, j *loadbalance.Upstream) int { + slices.SortStableFunc(activeUpstreams, func(i, j *loadbalance.Upstream) int { if i.Weight != j.Weight { // sort by weight DESC return cmp.Compare(j.Weight, i.Weight) @@ -43,7 +43,7 @@ func ReverseProxyUpstreamList(w http.ResponseWriter, r *http.Request) { return cmp.Compare(i.OriginIpOrDomain, j.OriginIpOrDomain) }) - slices.SortFunc(inactiveUpstreams, func(i, j *loadbalance.Upstream) int { + slices.SortStableFunc(inactiveUpstreams, func(i, j *loadbalance.Upstream) int { if i.Weight != j.Weight { // sort by weight DESC return cmp.Compare(j.Weight, i.Weight) From d64b1174af4bb0dacef315e139e3ca030e32f900 Mon Sep 17 00:00:00 2001 From: Kawin Viriyaprasopsook Date: Tue, 23 Jul 2024 08:20:59 +0700 Subject: [PATCH 4/8] =?UTF-8?q?=E2=8F=AA=20keep=20compatible=20with=20go?= =?UTF-8?q?=201.20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/upstreams.go | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/upstreams.go b/src/upstreams.go index 8241f04..3ab2eab 100644 --- a/src/upstreams.go +++ b/src/upstreams.go @@ -1,10 +1,9 @@ package main import ( - "cmp" "encoding/json" "net/http" - "slices" + "sort" "strings" "imuslab.com/zoraxy/mod/dynamicproxy/loadbalance" @@ -34,22 +33,19 @@ func ReverseProxyUpstreamList(w http.ResponseWriter, r *http.Request) { activeUpstreams := targetEndpoint.ActiveOrigins inactiveUpstreams := targetEndpoint.InactiveOrigins - slices.SortStableFunc(activeUpstreams, func(i, j *loadbalance.Upstream) int { - if i.Weight != j.Weight { - // sort by weight DESC - return cmp.Compare(j.Weight, i.Weight) + // Sort the upstreams slice by weight, then by origin domain alphabetically + sort.Slice(activeUpstreams, func(i, j int) bool { + if activeUpstreams[i].Weight != activeUpstreams[j].Weight { + return activeUpstreams[i].Weight > activeUpstreams[j].Weight } - // sort by origin ASC - return cmp.Compare(i.OriginIpOrDomain, j.OriginIpOrDomain) + return activeUpstreams[i].OriginIpOrDomain < activeUpstreams[j].OriginIpOrDomain }) - slices.SortStableFunc(inactiveUpstreams, func(i, j *loadbalance.Upstream) int { - if i.Weight != j.Weight { - // sort by weight DESC - return cmp.Compare(j.Weight, i.Weight) + sort.Slice(inactiveUpstreams, func(i, j int) bool { + if inactiveUpstreams[i].Weight != inactiveUpstreams[j].Weight { + return inactiveUpstreams[i].Weight > inactiveUpstreams[j].Weight } - // sort by origin ASC - return cmp.Compare(i.OriginIpOrDomain, j.OriginIpOrDomain) + return inactiveUpstreams[i].OriginIpOrDomain < inactiveUpstreams[j].OriginIpOrDomain }) type UpstreamCombinedList struct { From 97ff48ee705b559cc43f8f74607b1f7bc196cd04 Mon Sep 17 00:00:00 2001 From: Kawin Viriyaprasopsook Date: Tue, 23 Jul 2024 08:29:48 +0700 Subject: [PATCH 5/8] =?UTF-8?q?=F0=9F=94=A5=20origins=20already=20checked?= =?UTF-8?q?=20before=20getRandomUpstreamByWeight?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mod/dynamicproxy/loadbalance/originPicker.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/mod/dynamicproxy/loadbalance/originPicker.go b/src/mod/dynamicproxy/loadbalance/originPicker.go index ad5ddc4..b74dce8 100644 --- a/src/mod/dynamicproxy/loadbalance/originPicker.go +++ b/src/mod/dynamicproxy/loadbalance/originPicker.go @@ -106,9 +106,6 @@ func getRandomUpstreamByWeight(upstreams []*Upstream) (*Upstream, int, error) { if totalUpstreams == 1 { return upstreams[0], 0, nil } - if totalUpstreams == 0 { - return nil, -1, errors.New("no upstream servers available") - } totalWeight := 0 fallbackUpstreams := make([]*Upstream, 0) // List of upstreams with weight 0 From d17de5c20090f6bd2d42015ecac46597fe593d0d Mon Sep 17 00:00:00 2001 From: Kawin Viriyaprasopsook Date: Tue, 23 Jul 2024 08:50:10 +0700 Subject: [PATCH 6/8] =?UTF-8?q?=E2=9C=A8=20weighted=20random=20upstream?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dynamicproxy/loadbalance/originPicker.go | 51 +++++++++++-------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/src/mod/dynamicproxy/loadbalance/originPicker.go b/src/mod/dynamicproxy/loadbalance/originPicker.go index b74dce8..8971184 100644 --- a/src/mod/dynamicproxy/loadbalance/originPicker.go +++ b/src/mod/dynamicproxy/loadbalance/originPicker.go @@ -102,44 +102,53 @@ func (m *RouteManager) getSessionHandler(r *http.Request, upstreams []*Upstream) /* Functions related to random upstream picking */ // Get a random upstream by the weights defined in Upstream struct, return the upstream, index value and any error func getRandomUpstreamByWeight(upstreams []*Upstream) (*Upstream, int, error) { - totalUpstreams := len(upstreams) - if totalUpstreams == 1 { + // If there is only one upstream, return it + if len(upstreams) == 1 { return upstreams[0], 0, nil } + // Calculate total weight for upstreams with weight > 0 totalWeight := 0 - fallbackUpstreams := make([]*Upstream, 0) // List of upstreams with weight 0 - fallbackUpstreamsOriginalID := make([]int, 0) + fallbackUpstreams := make([]*Upstream, 0) - // Calculate total weight and gather fallbacks - for ix, upstream := range upstreams { - totalWeight += upstream.Weight - if upstream.Weight == 0 { - fallbackUpstreams = append(fallbackUpstreams, upstream) - fallbackUpstreamsOriginalID = append(fallbackUpstreamsOriginalID, ix) + for _, upstream := range upstreams { + if upstream.Weight > 0 { + totalWeight += upstream.Weight + } else { + fallbackUpstreams = append(fallbackUpstreams, upstream) // Collect fallback upstreams } } + // If there are no upstreams with weight > 0, return a fallback upstream if available if totalWeight == 0 { - // If total weight is 0, fallback to a random upstream with weight 0 - if len(fallbackUpstreams) == 0 { - return nil, -1, errors.New("no valid upstream servers available") + if len(fallbackUpstreams) > 0 { + // Randomly select one of the fallback upstreams + index := rand.Intn(len(fallbackUpstreams)) + return fallbackUpstreams[index], index, nil } - upstreamID := rand.Intn(len(fallbackUpstreams)) - return fallbackUpstreams[upstreamID], fallbackUpstreamsOriginalID[upstreamID], nil + // No upstreams available at all + return nil, -1, errors.New("no valid upstream servers available") } - // Generate a random number in the range of total weight - r := rand.Intn(totalWeight) + // Random weight between 0 and total weight + randomWeight := rand.Intn(totalWeight) - // Select upstream based on random number + // Select an upstream based on the random weight for i, upstream := range upstreams { - r -= upstream.Weight - if r < 0 { - return upstream, i, nil + if upstream.Weight > 0 { // Only consider upstreams with weight > 0 + if randomWeight < upstream.Weight { + return upstream, i, nil // Return the selected upstream and its index + } + randomWeight -= upstream.Weight } } + // If we reach here, it means we should return a fallback upstream if available + if len(fallbackUpstreams) > 0 { + index := rand.Intn(len(fallbackUpstreams)) + return fallbackUpstreams[index], index, nil + } + return nil, -1, errors.New("failed to pick an upstream origin server") } From e53c3cf3c4bd81200e85cbc25546dfa157716173 Mon Sep 17 00:00:00 2001 From: Kawin Viriyaprasopsook Date: Wed, 24 Jul 2024 14:47:33 +0700 Subject: [PATCH 7/8] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20fallbackUpstreams=20wi?= =?UTF-8?q?th=20preserve=20index?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dynamicproxy/loadbalance/originPicker.go | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/mod/dynamicproxy/loadbalance/originPicker.go b/src/mod/dynamicproxy/loadbalance/originPicker.go index 8971184..da4423e 100644 --- a/src/mod/dynamicproxy/loadbalance/originPicker.go +++ b/src/mod/dynamicproxy/loadbalance/originPicker.go @@ -107,15 +107,22 @@ func getRandomUpstreamByWeight(upstreams []*Upstream) (*Upstream, int, error) { return upstreams[0], 0, nil } + // Preserve the index with upstreams + type upstreamWithIndex struct { + Upstream *Upstream + Index int + } + // Calculate total weight for upstreams with weight > 0 totalWeight := 0 - fallbackUpstreams := make([]*Upstream, 0) + fallbackUpstreams := make([]upstreamWithIndex, 0, len(upstreams)) - for _, upstream := range upstreams { + for index, upstream := range upstreams { if upstream.Weight > 0 { totalWeight += upstream.Weight } else { - fallbackUpstreams = append(fallbackUpstreams, upstream) // Collect fallback upstreams + // Collect fallback upstreams + fallbackUpstreams = append(fallbackUpstreams, upstreamWithIndex{upstream, index}) } } @@ -124,7 +131,7 @@ func getRandomUpstreamByWeight(upstreams []*Upstream) (*Upstream, int, error) { if len(fallbackUpstreams) > 0 { // Randomly select one of the fallback upstreams index := rand.Intn(len(fallbackUpstreams)) - return fallbackUpstreams[index], index, nil + return fallbackUpstreams[index].Upstream, fallbackUpstreams[index].Index, nil } // No upstreams available at all return nil, -1, errors.New("no valid upstream servers available") @@ -134,10 +141,11 @@ func getRandomUpstreamByWeight(upstreams []*Upstream) (*Upstream, int, error) { randomWeight := rand.Intn(totalWeight) // Select an upstream based on the random weight - for i, upstream := range upstreams { + for index, upstream := range upstreams { if upstream.Weight > 0 { // Only consider upstreams with weight > 0 if randomWeight < upstream.Weight { - return upstream, i, nil // Return the selected upstream and its index + // Return the selected upstream and its index + return upstream, index, nil } randomWeight -= upstream.Weight } @@ -146,7 +154,7 @@ func getRandomUpstreamByWeight(upstreams []*Upstream) (*Upstream, int, error) { // If we reach here, it means we should return a fallback upstream if available if len(fallbackUpstreams) > 0 { index := rand.Intn(len(fallbackUpstreams)) - return fallbackUpstreams[index], index, nil + return fallbackUpstreams[index].Upstream, fallbackUpstreams[index].Index, nil } return nil, -1, errors.New("failed to pick an upstream origin server") From 8a8ec1cb0be387121ba13b0ac1b450952d80bd53 Mon Sep 17 00:00:00 2001 From: Kawin Viriyaprasopsook Date: Wed, 24 Jul 2024 14:59:48 +0700 Subject: [PATCH 8/8] =?UTF-8?q?=F0=9F=93=9D=20randIndex=20for=20fallbackUp?= =?UTF-8?q?streams=20random?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mod/dynamicproxy/loadbalance/originPicker.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mod/dynamicproxy/loadbalance/originPicker.go b/src/mod/dynamicproxy/loadbalance/originPicker.go index da4423e..ad77472 100644 --- a/src/mod/dynamicproxy/loadbalance/originPicker.go +++ b/src/mod/dynamicproxy/loadbalance/originPicker.go @@ -130,8 +130,8 @@ func getRandomUpstreamByWeight(upstreams []*Upstream) (*Upstream, int, error) { if totalWeight == 0 { if len(fallbackUpstreams) > 0 { // Randomly select one of the fallback upstreams - index := rand.Intn(len(fallbackUpstreams)) - return fallbackUpstreams[index].Upstream, fallbackUpstreams[index].Index, nil + randIndex := rand.Intn(len(fallbackUpstreams)) + return fallbackUpstreams[randIndex].Upstream, fallbackUpstreams[randIndex].Index, nil } // No upstreams available at all return nil, -1, errors.New("no valid upstream servers available") @@ -153,8 +153,8 @@ func getRandomUpstreamByWeight(upstreams []*Upstream) (*Upstream, int, error) { // If we reach here, it means we should return a fallback upstream if available if len(fallbackUpstreams) > 0 { - index := rand.Intn(len(fallbackUpstreams)) - return fallbackUpstreams[index].Upstream, fallbackUpstreams[index].Index, nil + randIndex := rand.Intn(len(fallbackUpstreams)) + return fallbackUpstreams[randIndex].Upstream, fallbackUpstreams[randIndex].Index, nil } return nil, -1, errors.New("failed to pick an upstream origin server")