phase3: HTTP server with converter, one-shot preview store, and middleware

This commit is contained in:
Sergey Filkin
2026-04-18 11:55:42 +03:00
parent d1682813ff
commit 843d8dc710
8 changed files with 1033 additions and 4 deletions
+91
View File
@@ -0,0 +1,91 @@
package server
import (
"context"
"sync"
"time"
"github.com/google/uuid"
)
const janitorInterval = 5 * time.Minute
type PreviewStore struct {
mu sync.Mutex
items map[string]previewItem
ttl time.Duration
now func() time.Time
}
type previewItem struct {
html []byte
mime string
filename string
expires time.Time
}
func NewPreviewStore(ttl time.Duration) *PreviewStore {
return &PreviewStore{
items: make(map[string]previewItem),
ttl: ttl,
now: time.Now,
}
}
func (s *PreviewStore) Put(html []byte, mime, filename string) string {
s.mu.Lock()
defer s.mu.Unlock()
id := uuid.NewString()
s.items[id] = previewItem{
html: append([]byte(nil), html...),
mime: mime,
filename: filename,
expires: s.now().Add(s.ttl),
}
return id
}
func (s *PreviewStore) Take(id string) (previewItem, bool) {
s.mu.Lock()
defer s.mu.Unlock()
item, ok := s.items[id]
if !ok {
return previewItem{}, false
}
delete(s.items, id)
if s.now().After(item.expires) {
return previewItem{}, false
}
item.html = append([]byte(nil), item.html...)
return item, true
}
func (s *PreviewStore) janitor(ctx context.Context) {
ticker := time.NewTicker(janitorInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case now := <-ticker.C:
s.cleanupExpired(now)
}
}
}
func (s *PreviewStore) cleanupExpired(now time.Time) {
s.mu.Lock()
defer s.mu.Unlock()
for id, item := range s.items {
if now.After(item.expires) {
delete(s.items, id)
}
}
}