aboutsummaryrefslogtreecommitdiffhomepage
path: root/pkg/net.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/net.go')
-rw-r--r--pkg/net.go109
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
+}