Lasa is a stateless proxy that generates a RSS or an Atom feed from a Standard.site publication. lasa.anhgelus.world
rss atom atprotocol standard-site atproto
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(cache): replace valkey by redis

+89 -76
+2 -4
Dockerfile
··· 4 4 5 5 COPY . . 6 6 7 - RUN apk add just rust 7 + RUN apk add just 8 8 9 - RUN GOFLAGS=-tags=musl just build 9 + RUN just build 10 10 11 11 FROM alpine:latest 12 12 ··· 14 14 15 15 # expose default port 16 16 EXPOSE 8000 17 - 18 - RUN apk add --no-cache libgcc 19 17 20 18 COPY --from=builder /app/build/lasad . 21 19 COPY --from=builder /app/build/lasa .
+3 -3
README.md
··· 38 38 just dev 39 39 ``` 40 40 41 - Starts Valkey in Docker and exposes its port: 41 + Starts Redis in Docker and exposes its port: 42 42 ```bash 43 - just valkey 43 + just redis 44 44 ``` 45 45 46 46 ## Deploy 47 47 48 48 Lasa is a standalone binary that requires nothing. 49 - You can use Valkey as a cache. 49 + You can use Redis as a cache. 50 50 51 51 ### Building 52 52
+2 -2
client.go
··· 6 6 "net/http" 7 7 "time" 8 8 9 - glide "github.com/valkey-io/valkey-glide/go/v2" 9 + "github.com/redis/go-redis/v9" 10 10 site "tangled.org/anhgelus.world/goat-site" 11 11 "tangled.org/anhgelus.world/xrpc" 12 12 "tangled.org/anhgelus.world/xrpc/atproto" ··· 15 15 func NewClient( 16 16 client *http.Client, 17 17 resolver *net.Resolver, 18 - cache *glide.Client, 18 + cache *redis.Client, 19 19 dur time.Duration, 20 20 host string, 21 21 ) xrpc.Client {
+14 -14
cmd/lasad/config/config.go
··· 3 3 import ( 4 4 "context" 5 5 "os" 6 + "strconv" 6 7 "time" 7 8 8 9 "github.com/BurntSushi/toml" 9 - glide "github.com/valkey-io/valkey-glide/go/v2" 10 - "github.com/valkey-io/valkey-glide/go/v2/config" 10 + "github.com/redis/go-redis/v9" 11 11 ) 12 12 13 13 const DefaultPath = "/etc/lasad.toml" ··· 35 35 ClientName string `toml:"client_name"` 36 36 } 37 37 38 - func (c *Cache) Connect() (*glide.Client, error) { 39 - cfg := config.NewClientConfiguration(). 40 - WithAddress(&config.NodeAddress{Host: c.Host, Port: int(c.Port)}) 38 + func (c *Cache) Connect() (*redis.Client, error) { 39 + opts := redis.Options{ 40 + Addr: c.Host + ":" + strconv.Itoa(int(c.Port)), 41 + DB: int(c.DB), 42 + } 41 43 if c.Auth != nil { 44 + if c.Auth.ClientName != "" { 45 + opts.ClientName = c.Auth.ClientName 46 + } 42 47 if c.Auth.Username != "" { 43 - cfg = cfg.WithCredentials(config.NewServerCredentials(c.Auth.Username, c.Auth.Password)) 44 - } else { 45 - cfg = cfg.WithCredentials(config.NewServerCredentialsWithDefaultUsername(c.Auth.Password)) 48 + opts.Username = c.Auth.Username 46 49 } 47 - } 48 - client, err := glide.NewClient(cfg) 49 - if err != nil { 50 - return nil, err 50 + opts.Password = c.Auth.Password 51 51 } 52 + client := redis.NewClient(&opts) 52 53 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 53 54 defer cancel() 54 - _, err = client.Ping(ctx) 55 - return client, err 55 + return client, client.Ping(ctx).Err() 56 56 } 57 57 58 58 func Load(path string) (*Config, error) {
+2 -2
cmd/lasad/default.toml
··· 6 6 # if you want to log HTTP 400 responses 7 7 #log_bad_request = true 8 8 9 - # if you are using valkey 9 + # if you are using redis 10 10 #[cache] 11 11 #host = "localhost" 12 12 #port = 6379 13 13 #db = 0 14 14 #duration = 60 # cache duration in minutes 15 15 16 - # if valkey requires auth 16 + # if redis requires auth 17 17 #[cache.auth] 18 18 #username = "" 19 19 #password = ""
+5 -4
cmd/lasad/run.go
··· 10 10 "syscall" 11 11 "time" 12 12 13 - glide "github.com/valkey-io/valkey-glide/go/v2" 13 + "github.com/redis/go-redis/v9" 14 14 "tangled.org/anhgelus.world/lasa" 15 15 "tangled.org/anhgelus.world/lasa/cmd/internal" 16 16 "tangled.org/anhgelus.world/lasa/cmd/lasad/config" ··· 45 45 if err != nil { 46 46 panic(err) 47 47 } 48 - var cache *glide.Client 48 + var cache *redis.Client 49 49 var dur time.Duration 50 50 if cfg.Cache != nil { 51 51 cache, err = cfg.Cache.Connect() 52 52 if err != nil { 53 - panic(err) 53 + slog.Error("cannot connect to redis", "error", err) 54 + os.Exit(3) 54 55 } 55 - slog.Info("connected to valkey") 56 + slog.Info("connected to redis") 56 57 dur = time.Duration(cfg.Cache.Duration) * time.Minute 57 58 } 58 59 client := lasa.NewClient(http.DefaultClient, net.DefaultResolver, cache, dur, cfg.Domain)
+12 -11
cmd/lasad/server/directory.go
··· 3 3 import ( 4 4 "bytes" 5 5 "context" 6 + "errors" 6 7 "fmt" 7 8 "html/template" 8 9 "io" ··· 10 11 "net/http" 11 12 "time" 12 13 13 - glide "github.com/valkey-io/valkey-glide/go/v2" 14 + "github.com/redis/go-redis/v9" 14 15 site "tangled.org/anhgelus.world/goat-site" 15 16 "tangled.org/anhgelus.world/lasa" 16 17 "tangled.org/anhgelus.world/lasa/cmd/lasad/config" ··· 19 20 ) 20 21 21 22 type Directory struct { 22 - cache *glide.Client 23 + cache *redis.Client 23 24 duration time.Duration 24 25 limiter *lasa.LimitManyRequests[[]byte] 25 26 } 26 27 27 - func NewDirectory(cache *glide.Client, dur time.Duration) *Directory { 28 + func NewDirectory(cache *redis.Client, dur time.Duration) *Directory { 28 29 return &Directory{ 29 30 cache: cache, 30 31 duration: dur, ··· 36 37 if d.cache == nil { 37 38 return nil 38 39 } 39 - resp, err := d.cache.Get(ctx, key) 40 - if err != nil || resp.IsNil() { 40 + res := d.cache.Get(ctx, key) 41 + err := res.Err() 42 + if err != nil { 43 + if !errors.Is(err, redis.Nil) { 44 + slog.Error("cannot fetch key in cache", "key", key) 45 + } 41 46 return nil 42 47 } 43 - return []byte(resp.Value()) 48 + return []byte(res.Val()) 44 49 } 45 50 46 51 func (d *Directory) toCache(ctx context.Context, key string, b []byte) { 47 52 if d.cache == nil { 48 53 return 49 54 } 50 - _, err := d.cache.Set(ctx, key, string(b)) 55 + err := d.cache.Set(ctx, key, string(b), d.duration).Err() 51 56 if err != nil { 52 57 slog.Warn("cannot set bytes in cache", "bytes", b, "error", err) 53 58 return 54 59 } 55 60 slog.Debug("bytes set in cache") 56 - _, err = d.cache.Expire(ctx, key, d.duration) 57 - if err != nil { 58 - slog.Warn("cannot set bytes expire", "bytes", b, "error", err) 59 - } 60 61 } 61 62 62 63 func (d *Directory) Author(ctx context.Context, did *atproto.DID) ([]byte, error) {
+2 -2
cmd/lasad/server/run.go
··· 9 9 "strings" 10 10 "time" 11 11 12 - glide "github.com/valkey-io/valkey-glide/go/v2" 12 + "github.com/redis/go-redis/v9" 13 13 "tangled.org/anhgelus.world/lasa" 14 14 "tangled.org/anhgelus.world/lasa/cmd/internal" 15 15 "tangled.org/anhgelus.world/lasa/cmd/lasad/config" ··· 26 26 RKey string 27 27 } 28 28 29 - func Run(ctx context.Context, cfg *config.Config, client xrpc.Client, cache *glide.Client, dur time.Duration) error { 29 + func Run(ctx context.Context, cfg *config.Config, client xrpc.Client, cache *redis.Client, dur time.Duration) error { 30 30 ctx = context.WithValue(ctx, keyCfg, cfg) 31 31 ctx = context.WithValue(ctx, keyClient, client) 32 32 ctx = context.WithValue(ctx, keyDir, NewDirectory(cache, dur))
+3 -3
compose.yml
··· 6 6 volumes: 7 7 - ./config.toml:/etc/lasad.toml 8 8 depends_on: 9 - - valkey 9 + - redis 10 10 dns: 11 11 # required, otherwise DNS server misbehaving 12 12 - 8.8.8.8 13 13 - 8.8.4.4 14 - valkey: 15 - image: valkey/valkey:alpine 14 + redis: 15 + image: docker.io/library/redis:alpine
+18 -16
directory.go
··· 3 3 import ( 4 4 "context" 5 5 "encoding/json" 6 + "errors" 6 7 "fmt" 7 8 "log/slog" 8 9 "time" 9 10 10 - glide "github.com/valkey-io/valkey-glide/go/v2" 11 + "github.com/redis/go-redis/v9" 11 12 "tangled.org/anhgelus.world/xrpc/atproto" 12 13 ) 13 14 14 15 type Directory struct { 15 16 inner atproto.Directory 16 - cache *glide.Client 17 + cache *redis.Client 17 18 duration time.Duration 18 19 limiter *LimitManyRequests[*atproto.DIDDocument] 19 20 } 20 21 21 - func NewDirectory(dir atproto.Directory, cache *glide.Client, dur time.Duration) *Directory { 22 + func NewDirectory(dir atproto.Directory, cache *redis.Client, dur time.Duration) *Directory { 22 23 return &Directory{ 23 24 inner: dir, 24 25 cache: cache, ··· 31 32 if d.cache == nil { 32 33 return nil 33 34 } 34 - resp, err := d.cache.Get(ctx, key) 35 + res := d.cache.Get(ctx, key) 35 36 var doc *atproto.DIDDocument 36 - if err == nil && !resp.IsNil() { 37 - b := resp.Value() 38 - err = json.Unmarshal([]byte(b), &doc) 39 - if err == nil { 40 - return doc 41 - } else { 42 - slog.Warn("cannot unmarshal cache response into DIDDocument", "resp", b) 37 + err := res.Err() 38 + if err != nil { 39 + if !errors.Is(err, redis.Nil) { 40 + slog.Error("cannot fetch key in cache", "key", key) 43 41 } 42 + return nil 43 + } 44 + b := res.Val() 45 + err = json.Unmarshal([]byte(b), &doc) 46 + if err == nil { 47 + return doc 48 + } else { 49 + slog.Warn("cannot unmarshal cache response into DIDDocument", "resp", b) 44 50 } 45 51 return nil 46 52 } ··· 54 60 slog.Warn("cannot marshal DIDDocument", "document", doc, "error", err) 55 61 return 56 62 } 57 - _, err = d.cache.Set(ctx, key, string(b)) 63 + err = d.cache.Set(ctx, key, string(b), d.duration).Err() 58 64 if err != nil { 59 65 slog.Warn("cannot set DIDDocument in cache", "document", doc, "error", err) 60 66 return 61 67 } 62 68 slog.Debug("DIDDocument set in cache") 63 - _, err = d.cache.Expire(ctx, key, d.duration) 64 - if err != nil { 65 - slog.Warn("cannot set DIDDocument expire", "document", doc, "error", err) 66 - } 67 69 } 68 70 69 71 func (d *Directory) ResolveHandle(ctx context.Context, h atproto.Handle) (*atproto.DIDDocument, error) {
+5 -3
go.mod
··· 5 5 require ( 6 6 github.com/BurntSushi/toml v1.6.0 7 7 github.com/nyttikord/logos v0.1.0 8 - github.com/valkey-io/valkey-glide/go/v2 v2.3.1 8 + github.com/redis/go-redis/v9 v9.18.0 9 9 tangled.org/anhgelus.world/goat-site v0.1.3 10 10 tangled.org/anhgelus.world/xrpc v0.4.0 11 11 ) 12 12 13 13 require ( 14 - github.com/google/go-cmp v0.7.0 // indirect 14 + github.com/cespare/xxhash/v2 v2.3.0 // indirect 15 + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 16 + github.com/stretchr/testify v1.8.4 // indirect 17 + go.uber.org/atomic v1.11.0 // indirect 15 18 golang.org/x/net v0.52.0 // indirect 16 - google.golang.org/protobuf v1.33.0 // indirect 17 19 )
+16 -8
go.sum
··· 1 1 github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= 2 2 github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 3 + github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= 4 + github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= 5 + github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= 6 + github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= 7 + github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 8 + github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 3 9 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 10 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 - github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 6 - github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 7 - github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 8 - github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 11 + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 12 + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 13 + github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= 14 + github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 9 15 github.com/nyttikord/logos v0.1.0 h1:uInB616bOrl7SvR6Bsv5Axjp7YHa57tidnyXRqp+cas= 10 16 github.com/nyttikord/logos v0.1.0/go.mod h1:gtgElduSi9ThHQ3kGMbf2rUXmU2ypwmvCRt8ivdWW+s= 11 17 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 12 18 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 19 + github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs= 20 + github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0= 13 21 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 14 22 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 15 - github.com/valkey-io/valkey-glide/go/v2 v2.3.1 h1:SB4wY7IjhmRh8WIBAgugoXimoW0mw9ZiGxDbKKhSagU= 16 - github.com/valkey-io/valkey-glide/go/v2 v2.3.1/go.mod h1:LK5zmODJa5xnxZndarh1trntExb3GVGJXz4GwDCagho= 23 + github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= 24 + github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= 25 + go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 26 + go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 17 27 golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= 18 28 golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= 19 - google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 20 - google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 21 29 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 22 30 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 23 31 pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
+5 -4
justfile
··· 1 1 builder := 'go build -ldflags "-s -w"' 2 2 testConfig := '"test.toml"' 3 - valkey_container := 'valkey' 3 + redis_container := 'redis' 4 4 5 5 docker := 'podman' 6 6 ··· 9 9 go run ./cmd/lasad/ -c {{testConfig}} -v 10 10 11 11 dev-docker: 12 + {{docker}} compose build 12 13 {{docker}} compose up -d 13 14 14 - valkey: 15 - {{docker}} run --rm --name {{valkey_container}} -p 6379:6379 -d docker.io/valkey/valkey:alpine 15 + redis: 16 + {{docker}} run --rm --name {{redis_container}} -p 6379:6379 -d docker.io/library/redis:alpine 16 17 17 18 stop: 18 - {{docker}} stop {{valkey_container}} 19 + {{docker}} stop {{redis_container}} 19 20 20 21 build: build-lasa build-lasad 21 22