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.

feat(cli): rate limits 400 and 404

+96 -5
+1
cmd/lasad/context.go
··· 6 6 keyCfg = iota 7 7 keyClient 8 8 keyDir 9 + keyLimiter 9 10 )
+73
cmd/lasad/limiter.go
··· 1 + package main 2 + 3 + import ( 4 + "log/slog" 5 + "math" 6 + "net/http" 7 + "strings" 8 + "sync" 9 + "time" 10 + ) 11 + 12 + type limited struct { 13 + time time.Time 14 + times uint 15 + } 16 + 17 + type Limiter struct { 18 + mu sync.RWMutex 19 + limited map[string]*limited 20 + } 21 + 22 + func exp(times, level uint) time.Duration { 23 + if int(times) < 4 { 24 + return 0 25 + } 26 + // max at 10800 seconds (3 hours) 27 + return min(time.Duration(math.Pow(2, float64(times+level))), 10800) * time.Second 28 + } 29 + 30 + func (l *Limiter) isLimited(r *http.Request) bool { 31 + ip, _, _ := strings.Cut(r.RemoteAddr, ":") 32 + l.mu.RLock() 33 + tm, ok := l.limited[ip] 34 + l.mu.RUnlock() 35 + return ok && tm.time.Unix() > time.Now().Unix() 36 + } 37 + 38 + func (l *Limiter) handle(w *statusWriter, r *http.Request, log *slog.Logger) { 39 + ip, _, _ := strings.Cut(r.RemoteAddr, ":") 40 + if w.code != http.StatusNotFound && w.code != http.StatusBadRequest && w.code != http.StatusTooManyRequests { 41 + return 42 + } 43 + var level uint = 0 44 + if w.code == http.StatusBadRequest { 45 + level = 1 46 + } 47 + 48 + l.mu.Lock() 49 + defer l.mu.Unlock() 50 + 51 + tm, ok := l.limited[ip] 52 + if !ok { 53 + l.limited[ip] = &limited{times: 1} 54 + return 55 + } 56 + tm.times++ 57 + dur := exp(tm.times, level) 58 + if dur == 0 { 59 + return 60 + } 61 + log.Info("rate limiting", "ip", ip, "for", dur) 62 + tm.time = time.Now().Add(dur) 63 + 64 + go func(l *Limiter, dur time.Duration, tm *limited, ip string) { 65 + time.Sleep(dur) 66 + l.mu.Lock() 67 + defer l.mu.Unlock() 68 + if time.Now().Unix() < tm.time.Unix() { 69 + return 70 + } 71 + delete(l.limited, ip) 72 + }(l, dur, tm, ip) 73 + }
+22 -5
cmd/lasad/run.go
··· 10 10 "os" 11 11 "os/signal" 12 12 "runtime/debug" 13 + "strings" 13 14 "syscall" 14 15 "time" 15 16 ··· 73 74 client := lasa.NewClient(http.DefaultClient, net.DefaultResolver, cache, dur, cfg.Domain) 74 75 ctx = context.WithValue(ctx, keyClient, client) 75 76 ctx = context.WithValue(ctx, keyDir, NewDirectory(cache, dur)) 77 + ctx = context.WithValue(ctx, keyLimiter, &Limiter{limited: make(map[string]*limited)}) 76 78 77 79 mux := http.NewServeMux() 78 80 mux.HandleFunc("GET /{id}/{rkey}/rss", func(w http.ResponseWriter, r *http.Request) { ··· 145 147 146 148 func middlewares(h http.Handler, ctx context.Context) http.Handler { 147 149 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 150 + if strings.HasPrefix(r.RequestURI, "/favicon.ico") { 151 + w.WriteHeader(http.StatusNotFound) 152 + return 153 + } 154 + limiter := ctx.Value(keyLimiter).(*Limiter) 148 155 status := &statusWriter{w, http.StatusOK} 149 156 log := slog.With("uri", r.RequestURI) 157 + if limiter.isLimited(r) { 158 + status.WriteHeader(http.StatusTooManyRequests) 159 + limiter.handle(status, r, log.With("status", http.StatusTooManyRequests)) 160 + return 161 + } 162 + 163 + now := time.Now() 150 164 defer func() { 151 165 if err := recover(); err != nil { 152 166 debug.PrintStack() 153 167 w.WriteHeader(http.StatusInternalServerError) 154 - log.Error("panic! (stack trace printed to stderr)") 168 + log.Error("panic! (stack trace printed to stderr)", "duration", time.Since(now)) 155 169 } 156 170 }() 157 - now := time.Now() 171 + 158 172 h.ServeHTTP(status, r.WithContext(ctx)) 173 + 159 174 log = log.With("status", status.code, "duration", time.Since(now)) 160 175 if status.code < 400 { 161 176 log.Debug("handled") 162 177 } else if status.code < 500 { 163 178 cfg := ctx.Value(keyCfg).(*config.Config) 179 + level := slog.LevelDebug 164 180 if (status.code == http.StatusNotFound && cfg.LogNotFound) || 165 181 (status.code == http.StatusBadRequest && cfg.LogBadRequest) || 166 182 (status.code != http.StatusNotFound && status.code != http.StatusBadRequest) { 167 - log.Warn("invalid request") 168 - } else { 169 - log.Debug("invalid request") 183 + level = slog.LevelWarn 170 184 } 185 + log.Log(context.Background(), level, "invalid request") 171 186 } else { 172 187 log.Error("error while handling request") 173 188 } 189 + 190 + limiter.handle(status, r, log) 174 191 }) 175 192 }