package dash import ( "context" "fmt" "io" "net/http" "net/url" "time" ) type downloadDeps interface { HTTPClient() *http.Client NewHTTPRequest(method string, url string, body io.Reader) (*http.Request, error) ReadAllContext(ctx context.Context, r io.Reader) ([]byte, error) Scheme() string UserAgent() string } type downloadConfig struct { authorization string begin time.Time currentRate int64 deps downloadDeps elapsedTarget int64 fqdn string } type downloadResult struct { elapsed float64 received int64 requestTicks float64 serverURL string timestamp int64 } func download(ctx context.Context, config downloadConfig) (downloadResult, error) { nbytes := (config.currentRate * 1000 * config.elapsedTarget) >> 3 var URL url.URL URL.Scheme = config.deps.Scheme() URL.Host = config.fqdn URL.Path = fmt.Sprintf("%s%d", downloadPath, nbytes) req, err := config.deps.NewHTTPRequest("GET", URL.String(), nil) var result downloadResult if err != nil { return result, err } result.serverURL = URL.String() req.Header.Set("User-Agent", config.deps.UserAgent()) req.Header.Set("Authorization", config.authorization) savedTicks := time.Now() resp, err := config.deps.HTTPClient().Do(req.WithContext(ctx)) if err != nil { return result, err } if resp.StatusCode != 200 { return result, errHTTPRequestFailed } defer resp.Body.Close() data, err := config.deps.ReadAllContext(ctx, resp.Body) if err != nil { return result, err } // Implementation note: MK contains a comment that says that Neubot uses // the elapsed time since when we start receiving the response but it // turns out that Neubot and MK do the same. So, we do what they do. At // the same time, we are currently not able to include the overhead that // is caused by HTTP headers etc. So, we're a bit less precise. result.elapsed = time.Since(savedTicks).Seconds() result.received = int64(len(data)) result.requestTicks = savedTicks.Sub(config.begin).Seconds() result.timestamp = time.Now().Unix() return result, nil }