All checks were successful
Test / Create distribution (push) Successful in 59s
Test / Hakurei (push) Successful in 4m20s
Test / ShareFS (push) Successful in 4m17s
Test / Hpkg (push) Successful in 4m56s
Test / Sandbox (race detector) (push) Successful in 5m8s
Test / Hakurei (race detector) (push) Successful in 6m7s
Test / Sandbox (push) Successful in 1m41s
Test / Flake checks (push) Successful in 1m55s
Avoid living under the default user agent and be at the mercy of some IDS. Signed-off-by: Ophestra <cat@gensokyo.uk>
96 lines
2.9 KiB
Go
96 lines
2.9 KiB
Go
package pkg
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"path"
|
|
"unique"
|
|
)
|
|
|
|
// An httpArtifact is an [Artifact] backed by a [http] url string. The method is
|
|
// hardcoded as [http.MethodGet]. Request body is not allowed because it cannot
|
|
// be deterministically represented by Params.
|
|
type httpArtifact struct {
|
|
// Caller-supplied url string.
|
|
url string
|
|
|
|
// Caller-supplied checksum of the response body. This is validated when
|
|
// closing the [io.ReadCloser] returned by Cure.
|
|
checksum unique.Handle[Checksum]
|
|
|
|
// doFunc is the Do method of [http.Client] supplied by the caller.
|
|
doFunc func(req *http.Request) (*http.Response, error)
|
|
}
|
|
|
|
var _ KnownChecksum = new(httpArtifact)
|
|
var _ fmt.Stringer = new(httpArtifact)
|
|
|
|
// NewHTTPGet returns a new [FileArtifact] backed by the supplied client. A GET
|
|
// request is set up for url. If c is nil, [http.DefaultClient] is used instead.
|
|
func NewHTTPGet(
|
|
c *http.Client,
|
|
url string,
|
|
checksum Checksum,
|
|
) FileArtifact {
|
|
if c == nil {
|
|
c = http.DefaultClient
|
|
}
|
|
return &httpArtifact{url: url, checksum: unique.Make(checksum), doFunc: c.Do}
|
|
}
|
|
|
|
// Kind returns the hardcoded [Kind] constant.
|
|
func (*httpArtifact) Kind() Kind { return KindHTTPGet }
|
|
|
|
// Params writes the backing url string. Client is not represented as it does
|
|
// not affect [Cache.Cure] outcome.
|
|
func (a *httpArtifact) Params(ctx *IContext) {
|
|
ctx.GetHash().Write([]byte(a.url))
|
|
}
|
|
|
|
// Dependencies returns a nil slice.
|
|
func (*httpArtifact) Dependencies() []Artifact { return nil }
|
|
|
|
// IsExclusive returns false: Cure returns as soon as a response is received.
|
|
func (*httpArtifact) IsExclusive() bool { return false }
|
|
|
|
// Checksum returns the caller-supplied checksum.
|
|
func (a *httpArtifact) Checksum() Checksum { return a.checksum.Value() }
|
|
|
|
// String returns [path.Base] over the backing url.
|
|
func (a *httpArtifact) String() string { return path.Base(a.url) }
|
|
|
|
// ResponseStatusError is returned for a response returned by an [http.Client]
|
|
// with a status code other than [http.StatusOK].
|
|
type ResponseStatusError int
|
|
|
|
func (e ResponseStatusError) Error() string {
|
|
return "the requested URL returned non-OK status: " + http.StatusText(int(e))
|
|
}
|
|
|
|
// Cure sends the http request and returns the resulting response body reader
|
|
// wrapped to perform checksum validation. It is valid but not encouraged to
|
|
// close the resulting [io.ReadCloser] before it is read to EOF, as that causes
|
|
// Close to block until all remaining data is consumed and validated.
|
|
func (a *httpArtifact) Cure(r *RContext) (rc io.ReadCloser, err error) {
|
|
var req *http.Request
|
|
req, err = http.NewRequestWithContext(r.Unwrap(), http.MethodGet, a.url, nil)
|
|
if err != nil {
|
|
return
|
|
}
|
|
req.Header.Set("User-Agent", "Hakurei/1.1")
|
|
|
|
var resp *http.Response
|
|
if resp, err = a.doFunc(req); err != nil {
|
|
return
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
_ = resp.Body.Close()
|
|
return nil, ResponseStatusError(resp.StatusCode)
|
|
}
|
|
|
|
rc = r.NewMeasuredReader(resp.Body, a.checksum)
|
|
return
|
|
}
|