orchard
mirrorEvery site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.
blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics
1package skills
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "io"
8 "net/http"
9 "strings"
10)
11
12// getJSON fetches and decodes. Every upstream here answers a block page rather
13// than JSON to a non-browser agent, so the User-Agent is not optional.
14func getJSON(ctx context.Context, d Deps, url string, out any) error {
15 req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
16 if err != nil {
17 return err
18 }
19 req.Header.Set("User-Agent", d.UA)
20 req.Header.Set("Accept", "application/json")
21
22 client := d.HTTP
23 if client == nil {
24 client = http.DefaultClient
25 }
26 resp, err := client.Do(req)
27 if err != nil {
28 return err
29 }
30 defer resp.Body.Close()
31 if resp.StatusCode != http.StatusOK {
32 return fmt.Errorf("%s: %s", url, resp.Status)
33 }
34 return json.NewDecoder(io.LimitReader(resp.Body, 4<<20)).Decode(out)
35}
36
37func containsAny(s string, subs ...string) bool {
38 for _, sub := range subs {
39 if strings.Contains(s, sub) {
40 return true
41 }
42 }
43 return false
44}
45
46func round2(v float64) float64 { return float64(int64(v*100+0.5)) / 100 }
47
48func abs(v float64) float64 {
49 if v < 0 {
50 return -v
51 }
52 return v
53}
54
55func readAll(resp *http.Response) ([]byte, error) {
56 return io.ReadAll(io.LimitReader(resp.Body, 4<<20))
57}
58
59func decode(body []byte, out any) error {
60 return json.Unmarshal(body, out)
61}