Skip to main content
5 of 6
Don't repeat tags in the title
Toby Speight
  • 81.8k
  • 14
  • 101
  • 309

Download artefacts using HTTP

The public code is SaveAsFile(). It takes a URL, checksum and authentication info, and downloads the artefact and its hash HTTP. It saves the content as a file and returns the file's path on success, otherwise an error.

As I'm fairly new to Go I would like to get some feedback on the error handling and on any ways to write this code more elegantly?

https://go.dev/play/p/8i_xZ-EVTvP

import (
    "io"
    "io/ioutil"
    "net/http"
    "os"
    "strings"
    "time"

    "github.com/pkg/errors"
)

var errCode = errors.New("bad response status code")
var errFileNotWritten = errors.New("file not written")
var errDigest = errors.New("digest are not identical")

const (
    statusMsg        = `code: %d, response body:\n%s`
    notWritten       = `unable to write file %s from Response body`
    msgDigestFailure = `target: \"%s\", file: \"%s\")`
    msgFileNotClosed = `unable to close file %s`
)

func respBody(url string, user string, pass string) (io.ReadCloser, error) {
    client := &http.Client{
        Timeout: time.Second * 10,
    }
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        return nil, err
    }

    if user != "" && pass != "" {
        req.SetBasicAuth(user, pass)
    }
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    if resp.StatusCode < 200 || resp.StatusCode >= 300 {
        return nil, errors.Wrapf(errCode, statusMsg, resp.StatusCode, resp.Body)
    }
    return resp.Body, nil
}

func classify(url string, user string, pass string) (string, error) {
    const suffix = ".sha512"
    rb, err := respBody(url+suffix, user, pass)
    if err != nil {
        return "", err
    }
    defer rb.Close()
    bodyBytes, err := ioutil.ReadAll(rb)
    if err != nil {
        return "", err
    }

    return string(bodyBytes), nil
}

func downFile(url string, user string, pass string) (*os.File, error) {
    responseBody, err := respBody(url, user, pass)
    if err != nil {
        return nil, err
    }
    defer responseBody.Close()

    file, err := ioutil.TempFile("", "*")
    if err != nil {
        return nil, err
    }
    written, err := io.Copy(file, responseBody)
    if err != nil {
        return nil, errors.Wrapf(err, notWritten, file.Name())
    } else if written <= 0 {
        return nil, errors.Wrapf(errFileNotWritten, notWritten, file.Name())
    }

    if err := file.Close(); err != nil {
        return nil, errors.Wrapf(err, msgFileNotClosed, file.Name())
    }

    return file, nil
}


func SaveAsFile(url string, digest string, user string, pass string) (string, error) {
    file, err := downFile(url, user, pass)
    if err != nil {
        return "", err
    }
    if digest != "" {
        targetDigest, err := classify(url, user, pass)
        if err != nil {
            return "", err
        }
        if strings.Compare(targetDigest, digest) != 0 {
            return "", errors.Wrapf(errDigest, msgDigestFailure, targetDigest, digest)
        }
    }
    return file.Name(), nil
}
Alberto
  • 173
  • 4