···231231 return rp, nil
232232}
233233234234-func startDecayMapCleanup(ctx context.Context, s *libanubis.Server) {
235235- ticker := time.NewTicker(1 * time.Hour)
236236- defer ticker.Stop()
237237-238238- for {
239239- select {
240240- case <-ticker.C:
241241- s.CleanupDecayMap()
242242- case <-ctx.Done():
243243- return
244244- }
245245- }
246246-}
247247-248234func main() {
249235 flagenv.Parse()
250236 flag.Parse()
···421407 wg.Add(1)
422408 go metricsServer(ctx, wg.Done)
423409 }
424424- go startDecayMapCleanup(ctx, s)
425410426411 var h http.Handler
427412 h = s
+1
docs/docs/CHANGELOG.md
···2424- Remove the "Success" interstitial after a proof of work challenge is concluded.
2525- Anubis now has the concept of [storage backends](./admin/policies.mdx#storage-backends). These allow you to change how Anubis stores temporary data (in memory, on the disk, or in Valkey). If you run Anubis in an environment where you have a low amount of memory available for Anubis (eg: less than 64 megabytes), be sure to configure the [`bbolt`](./admin/policies.mdx#bbolt) storage backend.
2626- The challenge issuance and validation process has been rewritten from scratch. Instead of generating challenge strings from request metadata (under the assumption that the values being compared against are stable), Anubis now generates random data for each challenge. This data is stored in the active [storage backend](./admin/policies.mdx#storage-backends) for up to 30 minutes. Fixes [#564](https://github.com/TecharoHQ/anubis/issues/564), [#746](https://github.com/TecharoHQ/anubis/issues/746), and other similar instances of this issue.
2727+- Make the [Open Graph](./admin/configuration/open-graph.mdx) subsystem and DNSBL subsystem use [storage backends](./admin/policies.mdx#storage-backends) instead of storing everything in memory by default.
2728- Add option for forcing a specific language ([#742](https://github.com/TecharoHQ/anubis/pull/742))
2829- Add translation for Turkish language ([#751](https://github.com/TecharoHQ/anubis/pull/751))
2930- Allow [Common Crawl](https://commoncrawl.org/) by default so scrapers have less incentive to scrape
+7-6
internal/ogtags/cache.go
···11package ogtags
2233import (
44+ "context"
45 "errors"
56 "log/slog"
67 "net/url"
···89)
9101011// GetOGTags is the main function that retrieves Open Graph tags for a URL
1111-func (c *OGTagCache) GetOGTags(url *url.URL, originalHost string) (map[string]string, error) {
1212+func (c *OGTagCache) GetOGTags(ctx context.Context, url *url.URL, originalHost string) (map[string]string, error) {
1213 if url == nil {
1314 return nil, errors.New("nil URL provided, cannot fetch OG tags")
1415 }
···2122 cacheKey := c.generateCacheKey(target, originalHost)
22232324 // Check cache first
2424- if cachedTags := c.checkCache(cacheKey); cachedTags != nil {
2525+ if cachedTags := c.checkCache(ctx, cacheKey); cachedTags != nil {
2526 return cachedTags, nil
2627 }
27282829 // Fetch HTML content, passing the original host
2929- doc, err := c.fetchHTMLDocumentWithCache(target, originalHost, cacheKey)
3030+ doc, err := c.fetchHTMLDocumentWithCache(ctx, target, originalHost, cacheKey)
3031 if errors.Is(err, syscall.ECONNREFUSED) {
3132 slog.Debug("Connection refused, returning empty tags")
3233 return nil, nil
···4243 ogTags := c.extractOGTags(doc)
43444445 // Store in cache
4545- c.cache.Set(cacheKey, ogTags, c.ogTimeToLive)
4646+ c.cache.Set(ctx, cacheKey, ogTags, c.ogTimeToLive)
46474748 return ogTags, nil
4849}
···5960}
60616162// checkCache checks if we have the tags cached and returns them if so
6262-func (c *OGTagCache) checkCache(cacheKey string) map[string]string {
6363- if cachedTags, ok := c.cache.Get(cacheKey); ok {
6363+func (c *OGTagCache) checkCache(ctx context.Context, cacheKey string) map[string]string {
6464+ if cachedTags, err := c.cache.Get(ctx, cacheKey); err == nil {
6465 slog.Debug("cache hit", "tags", cachedTags)
6566 return cachedTags
6667 }
+13-12
internal/ogtags/cache_test.go
···99 "time"
10101111 "github.com/TecharoHQ/anubis/lib/policy/config"
1212+ "github.com/TecharoHQ/anubis/lib/store/memory"
1213)
13141415func TestCacheReturnsDefault(t *testing.T) {
···2122 TimeToLive: time.Minute,
2223 ConsiderHost: false,
2324 Override: want,
2424- })
2525+ }, memory.New(t.Context()))
25262627 u, err := url.Parse("https://anubis.techaro.lol")
2728 if err != nil {
2829 t.Fatal(err)
2930 }
30313131- result, err := cache.GetOGTags(u, "anubis.techaro.lol")
3232+ result, err := cache.GetOGTags(t.Context(), u, "anubis.techaro.lol")
3233 if err != nil {
3334 t.Fatal(err)
3435 }
···4950 Enabled: true,
5051 TimeToLive: time.Minute,
5152 ConsiderHost: false,
5252- })
5353+ }, memory.New(t.Context()))
53545455 // Set up test data
5556 urlStr := "http://example.com/page"
···6061 cacheKey := cache.generateCacheKey(urlStr, "example.com")
61626263 // Test cache miss
6363- tags := cache.checkCache(cacheKey)
6464+ tags := cache.checkCache(t.Context(), cacheKey)
6465 if tags != nil {
6566 t.Errorf("expected nil tags on cache miss, got %v", tags)
6667 }
67686869 // Manually add to cache
6969- cache.cache.Set(cacheKey, expectedTags, time.Minute)
7070+ cache.cache.Set(t.Context(), cacheKey, expectedTags, time.Minute)
70717172 // Test cache hit
7272- tags = cache.checkCache(cacheKey)
7373+ tags = cache.checkCache(t.Context(), cacheKey)
7374 if tags == nil {
7475 t.Fatal("expected non-nil tags on cache hit, got nil")
7576 }
···112113 Enabled: true,
113114 TimeToLive: time.Minute,
114115 ConsiderHost: false,
115115- })
116116+ }, memory.New(t.Context()))
116117117118 // Parse the test server URL
118119 parsedURL, err := url.Parse(ts.URL)
···122123123124 // Test fetching OG tags from the test server
124125 // Pass the host from the parsed test server URL
125125- ogTags, err := cache.GetOGTags(parsedURL, parsedURL.Host)
126126+ ogTags, err := cache.GetOGTags(t.Context(), parsedURL, parsedURL.Host)
126127 if err != nil {
127128 t.Fatalf("failed to get OG tags: %v", err)
128129 }
···142143143144 // Test fetching OG tags from the cache
144145 // Pass the host from the parsed test server URL
145145- ogTags, err = cache.GetOGTags(parsedURL, parsedURL.Host)
146146+ ogTags, err = cache.GetOGTags(t.Context(), parsedURL, parsedURL.Host)
146147 if err != nil {
147148 t.Fatalf("failed to get OG tags from cache: %v", err)
148149 }
149150150151 // Test fetching OG tags from the cache (3rd time)
151152 // Pass the host from the parsed test server URL
152152- newOgTags, err := cache.GetOGTags(parsedURL, parsedURL.Host)
153153+ newOgTags, err := cache.GetOGTags(t.Context(), parsedURL, parsedURL.Host)
153154 if err != nil {
154155 t.Fatalf("failed to get OG tags from cache: %v", err)
155156 }
···263264 Enabled: true,
264265 TimeToLive: time.Minute,
265266 ConsiderHost: tc.ogCacheConsiderHost,
266266- })
267267+ }, memory.New(t.Context()))
267268268269 for i, req := range tc.requests {
269269- ogTags, err := cache.GetOGTags(parsedURL, req.host)
270270+ ogTags, err := cache.GetOGTags(t.Context(), parsedURL, req.host)
270271 if err != nil {
271272 t.Errorf("Request %d (host: %s): unexpected error: %v", i+1, req.host, err)
272273 continue // Skip further checks for this request if error occurred
+4-4
internal/ogtags/fetch.go
···20202121// fetchHTMLDocumentWithCache fetches the HTML document from the given URL string,
2222// preserving the original host header.
2323-func (c *OGTagCache) fetchHTMLDocumentWithCache(urlStr string, originalHost string, cacheKey string) (*html.Node, error) {
2424- req, err := http.NewRequestWithContext(context.Background(), "GET", urlStr, nil)
2323+func (c *OGTagCache) fetchHTMLDocumentWithCache(ctx context.Context, urlStr string, originalHost string, cacheKey string) (*html.Node, error) {
2424+ req, err := http.NewRequestWithContext(ctx, "GET", urlStr, nil)
2525 if err != nil {
2626 return nil, fmt.Errorf("failed to create http request: %w", err)
2727 }
···4141 var netErr net.Error
4242 if errors.As(err, &netErr) && netErr.Timeout() {
4343 slog.Debug("og: request timed out", "url", urlStr)
4444- c.cache.Set(cacheKey, emptyMap, c.ogTimeToLive/2) // Cache empty result for half the TTL to not spam the server
4444+ c.cache.Set(ctx, cacheKey, emptyMap, c.ogTimeToLive/2) // Cache empty result for half the TTL to not spam the server
4545 }
4646 return nil, fmt.Errorf("http get failed: %w", err)
4747 }
···56565757 if resp.StatusCode != http.StatusOK {
5858 slog.Debug("og: received non-OK status code", "url", urlStr, "status", resp.StatusCode)
5959- c.cache.Set(cacheKey, emptyMap, c.ogTimeToLive) // Cache empty result for non-successful status codes
5959+ c.cache.Set(ctx, cacheKey, emptyMap, c.ogTimeToLive) // Cache empty result for non-successful status codes
6060 return nil, fmt.Errorf("%w: page not found", ErrOgHandled)
6161 }
6262