aboutsummaryrefslogtreecommitdiff
path: root/cmd/xedn/main.go
blob: b5f861a3704dbd522f88e7f7f309383fa4a2f557 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// Command quiche is a little cute cache server for my B2 bucket.
package main

import (
	"bytes"
	"context"
	"encoding/gob"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"

	"github.com/golang/groupcache"
	"github.com/sebest/xff"
	"tailscale.com/tsnet"
	"tailscale.com/tsweb"
	"within.website/ln"
	"within.website/ln/ex"
	"within.website/ln/opname"
	"within.website/x/internal"
	"within.website/x/web"
)

var (
	b2Backend = flag.String("b2-backend", "https://f001.backblazeb2.com", "Backblaze B2 base URL")
	addr      = flag.String("addr", ":8080", "server address")
)

const cacheSize = 128 * 1024 * 1024 // 128 mebibytes

type CacheData struct {
	Headers http.Header
	Body    []byte
}

var Group = groupcache.NewGroup("b2-bucket", cacheSize, groupcache.GetterFunc(
	func(ctx groupcache.Context, key string, dest groupcache.Sink) error {
		ln.Log(context.Background(), ln.F{"key": key})

		resp, err := http.Get(*b2Backend + key)
		if err != nil {
			return fmt.Errorf("can't fetch from b2: %v", err)
		}
		defer resp.Body.Close()

		if resp.StatusCode != http.StatusOK {
			return web.NewError(http.StatusOK, resp)
		}

		body, err := io.ReadAll(resp.Body)
		if err != nil {
			return fmt.Errorf("can't read from b2: %v", err)
		}

		result := &CacheData{
			Headers: resp.Header,
			Body:    body,
		}

		var buf bytes.Buffer
		err = gob.NewEncoder(&buf).Encode(result)
		if err != nil {
			return err
		}

		dest.SetBytes(buf.Bytes())

		return nil
	},
))

func main() {
	internal.HandleStartup()
	ctx := opname.With(context.Background(), "startup")

	go func () {
		srv := &tsnet.Server{
			Hostname: "xedn-" + os.Getenv("FLY_REGION"),
			Logf: log.New(io.Discard, "", 0).Printf,
			AuthKey:   os.Getenv("TS_AUTHKEY"),
		}

		lis, err := srv.Listen("tcp", ":80")
		if err != nil {
			ln.FatalErr(ctx, err, ln.Action("tsnet listening"))
		}

		http.DefaultServeMux.HandleFunc("/debug/varz", tsweb.VarzHandler)

		defer srv.Close()
		defer lis.Close()
		ln.FatalErr(opname.With(ctx, "metrics-tsnet"), http.Serve(lis, ex.HTTPLog(http.DefaultServeMux)))
	} ()

	xffMW, err := xff.Default()
	if err != nil {
		ln.FatalErr(ctx, err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("/file/christine-static/", func(w http.ResponseWriter, r *http.Request) {
		var b []byte
		err := Group.Get(nil, r.URL.Path, groupcache.AllocatingByteSliceSink(&b))
		if err != nil {
			http.Error(w, err.Error(), http.StatusNotFound)
			return
		}

		var result CacheData
		err = gob.NewDecoder(bytes.NewBuffer(b)).Decode(&result)
		if err != nil {
			ln.Error(r.Context(), err)
			http.Error(w, "internal cache error", http.StatusInternalServerError)
			return
		}

		for k, vs := range result.Headers {
			for _, v := range vs {
				w.Header().Add(k, v)
			}
		}
		w.WriteHeader(http.StatusOK)
		w.Write(result.Body)
	})

	ln.Log(context.Background(), ln.F{"addr": *addr})
	http.ListenAndServe(*addr, xffMW.Handler(ex.HTTPLog(mux)))
}