diff options
Diffstat (limited to 'pkg/net.go')
| -rw-r--r-- | pkg/net.go | 109 |
1 files changed, 109 insertions, 0 deletions
diff --git a/pkg/net.go b/pkg/net.go new file mode 100644 index 00000000..cd30e3bb --- /dev/null +++ b/pkg/net.go @@ -0,0 +1,109 @@ +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()) + }) +} + +// Inputs returns a nil slice. +func (*httpArtifact) Inputs() []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)) +} + +// IsExecutable returns false. +func (*httpArtifact) IsExecutable() bool { return false } + +// 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 +} |
