7df25795c0
See https://github.com/ooni/probe/issues/2147. Note that this PR also tries to reduce usage of legacy names inside unit/integration tests.
146 lines
3.8 KiB
Go
146 lines
3.8 KiB
Go
package probeservices
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/ooni/probe-cli/v3/internal/model"
|
|
)
|
|
|
|
// Default returns the default probe services
|
|
func Default() []model.OOAPIService {
|
|
return []model.OOAPIService{{
|
|
Address: "https://api.ooni.io",
|
|
Type: "https",
|
|
}, {
|
|
Front: "dkyhjv0wpi2dk.cloudfront.net",
|
|
Type: "cloudfront",
|
|
Address: "https://dkyhjv0wpi2dk.cloudfront.net",
|
|
}}
|
|
}
|
|
|
|
// SortEndpoints gives priority to https, then cloudfronted, then onion.
|
|
func SortEndpoints(in []model.OOAPIService) (out []model.OOAPIService) {
|
|
for _, entry := range in {
|
|
if entry.Type == "https" {
|
|
out = append(out, entry)
|
|
}
|
|
}
|
|
for _, entry := range in {
|
|
if entry.Type == "cloudfront" {
|
|
out = append(out, entry)
|
|
}
|
|
}
|
|
for _, entry := range in {
|
|
if entry.Type == "onion" {
|
|
out = append(out, entry)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// OnlyHTTPS returns the HTTPS endpoints only.
|
|
func OnlyHTTPS(in []model.OOAPIService) (out []model.OOAPIService) {
|
|
for _, entry := range in {
|
|
if entry.Type == "https" {
|
|
out = append(out, entry)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// OnlyFallbacks returns the fallback endpoints only.
|
|
func OnlyFallbacks(in []model.OOAPIService) (out []model.OOAPIService) {
|
|
for _, entry := range SortEndpoints(in) {
|
|
if entry.Type != "https" {
|
|
out = append(out, entry)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Candidate is a candidate probe service.
|
|
type Candidate struct {
|
|
// Duration is the time it took to access the service.
|
|
Duration time.Duration
|
|
|
|
// Err indicates whether the service works.
|
|
Err error
|
|
|
|
// Endpoint is the service endpoint.
|
|
Endpoint model.OOAPIService
|
|
|
|
// TestHelpers contains the data returned by the endpoint.
|
|
TestHelpers map[string][]model.OOAPIService
|
|
}
|
|
|
|
func (c *Candidate) try(ctx context.Context, sess Session) {
|
|
client, err := NewClient(sess, c.Endpoint)
|
|
if err != nil {
|
|
c.Err = err
|
|
return
|
|
}
|
|
start := time.Now()
|
|
testhelpers, err := client.GetTestHelpers(ctx)
|
|
c.Duration = time.Since(start)
|
|
c.Err = err
|
|
c.TestHelpers = testhelpers
|
|
sess.Logger().Debugf("probe services: %+v: %+v %s", c.Endpoint, err, c.Duration)
|
|
}
|
|
|
|
func try(ctx context.Context, sess Session, svc model.OOAPIService) *Candidate {
|
|
candidate := &Candidate{Endpoint: svc}
|
|
candidate.try(ctx, sess)
|
|
return candidate
|
|
}
|
|
|
|
// TryAll tries all the input services using the provided context and session. It
|
|
// returns a list containing information on each candidate that was tried. We will
|
|
// try all the HTTPS candidates first. So, the beginning of the list will contain
|
|
// all of them, and for each of them you will know whether it worked (by checking the
|
|
// Err field) and how fast it was (by checking the Duration field). You should pick
|
|
// the fastest one that worked. If none of them works, then TryAll will subsequently
|
|
// attempt with all the available fallbacks, and return at the first success. In
|
|
// such case, you will see a list of N failing HTTPS candidates, followed by a single
|
|
// successful fallback candidate (e.g. cloudfronted). If all candidates fail, you
|
|
// see in output a list containing all entries where Err is not nil.
|
|
func TryAll(ctx context.Context, sess Session, in []model.OOAPIService) (out []*Candidate) {
|
|
var found bool
|
|
for _, svc := range OnlyHTTPS(in) {
|
|
candidate := try(ctx, sess, svc)
|
|
out = append(out, candidate)
|
|
if candidate.Err == nil {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
for _, svc := range OnlyFallbacks(in) {
|
|
candidate := try(ctx, sess, svc)
|
|
out = append(out, candidate)
|
|
if candidate.Err == nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// SelectBest selects the best among the candidates. If there is no
|
|
// suitable candidate, then this function returns nil.
|
|
func SelectBest(candidates []*Candidate) (selected *Candidate) {
|
|
for _, e := range candidates {
|
|
if e.Err != nil {
|
|
continue
|
|
}
|
|
if selected == nil {
|
|
selected = e
|
|
continue
|
|
}
|
|
if selected.Duration > e.Duration {
|
|
selected = e
|
|
continue
|
|
}
|
|
}
|
|
return
|
|
}
|