Files
hakurei/internal/pkg/net.go
Ophestra f712466714
All checks were successful
Test / Create distribution (push) Successful in 47s
Test / Sandbox (push) Successful in 2m50s
Test / ShareFS (push) Successful in 4m46s
Test / Sandbox (race detector) (push) Successful in 5m16s
Test / Hpkg (push) Successful in 5m24s
Test / Hakurei (push) Successful in 5m40s
Test / Hakurei (race detector) (push) Successful in 7m27s
Test / Flake checks (push) Successful in 1m42s
internal/pkg: move dependency flooding to cache
This imposes a hard upper limit to concurrency during dependency satisfaction and moves all dependency-related code out of individual implementations of Artifact. This change also includes ctx and msg as part of Cache.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2026-01-09 05:20:34 +09:00

125 lines
3.1 KiB
Go

package pkg
import (
"context"
"crypto/sha512"
"io"
"net/http"
"sync"
)
// 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 context.
ctx context.Context
// Caller-supplied url string.
url string
// Caller-supplied checksum of the response body. This is validated during
// curing and the first call to Data.
checksum Checksum
// doFunc is the Do method of [http.Client] supplied by the caller.
doFunc func(req *http.Request) (*http.Response, error)
// Response body read to EOF.
data []byte
// Synchronises access to data.
mu sync.Mutex
}
var _ KnownChecksum = new(httpArtifact)
// NewHTTPGet returns a new [File] backed by the supplied client. A GET request
// is set up for url. If c is nil, [http.DefaultClient] is used instead.
func NewHTTPGet(
ctx context.Context,
c *http.Client,
url string,
checksum Checksum,
) File {
if ctx == nil {
ctx = context.Background()
}
if c == nil {
c = http.DefaultClient
}
return &httpArtifact{ctx: ctx, url: url, checksum: checksum, doFunc: c.Do}
}
// Kind returns the hardcoded [Kind] constant.
func (a *httpArtifact) Kind() Kind { return KindHTTPGet }
// Params returns the backing url string. Context is not represented as it does
// not affect [Cache.Cure] outcome.
func (a *httpArtifact) Params() []byte { return []byte(a.url) }
// Dependencies returns a nil slice.
func (a *httpArtifact) Dependencies() []Artifact { return nil }
// Checksum returns the caller-supplied checksum.
func (a *httpArtifact) Checksum() Checksum { return a.checksum }
// 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))
}
// do sends the caller-supplied request on the caller-supplied [http.Client]
// and reads its response body to EOF and returns the resulting bytes.
func (a *httpArtifact) do() (data []byte, err error) {
var req *http.Request
req, err = http.NewRequestWithContext(a.ctx, 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)
}
if data, err = io.ReadAll(resp.Body); err != nil {
_ = resp.Body.Close()
return
}
err = resp.Body.Close()
return
}
// Cure completes the http request and returns the resulting response body read
// to EOF. Data does not interact with the filesystem.
func (a *httpArtifact) Cure() (data []byte, err error) {
a.mu.Lock()
defer a.mu.Unlock()
if a.data != nil {
// validated by cache or a previous call to Data
return a.data, nil
}
if data, err = a.do(); err != nil {
return
}
h := sha512.New384()
h.Write(data)
if got := (Checksum)(h.Sum(nil)); got != a.checksum {
return nil, &ChecksumMismatchError{got, a.checksum}
}
a.data = data
return
}