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 } 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 }