All checks were successful
Test / Create distribution (push) Successful in 1m0s
Test / Sandbox (push) Successful in 2m41s
Test / Hakurei (push) Successful in 4m1s
Test / ShareFS (push) Successful in 4m1s
Test / Hpkg (push) Successful in 4m35s
Test / Sandbox (race detector) (push) Successful in 5m4s
Test / Hakurei (race detector) (push) Successful in 6m0s
Test / Flake checks (push) Successful in 1m46s
This should hopefully provide good separation between the artifact curing backend implementation and the (still work in progress) language. Making the IR parseable also guarantees uniqueness of the representation. Signed-off-by: Ophestra <cat@gensokyo.uk>
107 lines
3.1 KiB
Go
107 lines
3.1 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]
|
|
|
|
// client is the address of the caller-supplied [http.Client].
|
|
client *http.Client
|
|
}
|
|
|
|
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 {
|
|
return &httpArtifact{url: url, checksum: unique.Make(checksum), client: c}
|
|
}
|
|
|
|
// 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.WriteString(a.url) }
|
|
|
|
func init() {
|
|
register(KindHTTPGet, func(r *IRReader) Artifact {
|
|
url := r.ReadString()
|
|
checksum, ok := r.Finalise()
|
|
if !ok {
|
|
panic(ErrExpectedChecksum)
|
|
}
|
|
return NewHTTPGet(nil, url, checksum.Value())
|
|
})
|
|
}
|
|
|
|
// 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")
|
|
|
|
c := a.client
|
|
if c == nil {
|
|
c = http.DefaultClient
|
|
}
|
|
|
|
var resp *http.Response
|
|
if resp, err = c.Do(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
|
|
}
|