Minimalistyczny headless CMS w Go: architektura i implementacja
Deweloperzy landingów często potrzebują prostego backendu: REST API do zgłoszeń i aktualności, panel administracyjny, integracja z CRM typu Bitrix24. LightHeadless rozwiązuje ten problem bez frameworków, ORM i zewnętrznych magazynów — tylko standardowa biblioteka Go, modernc/sqlite oraz 125 testów. Binarka kompiluje się z CGO_ENABLED=0 i działa na dowolnym VPS.
Czysty Go SQLite bez CGO
Kluczowy wybór to modernc.org/sqlite zamiast mattn/go-sqlite3. To pełna implementacja SQLite w Go z kompatybilnym interfejsem database/sql, bez potrzeby kompilatora C.
import (
"database/sql"
_ "modernc.org/sqlite"
)
func Open(path string) (*sql.DB, error) {
db, err := sql.Open("sqlite", path)
if err != nil {
return nil, err
}
// Tryb WAL dla współbieżnego dostępu
pragmas := []string{
"PRAGMA journal_mode=WAL",
"PRAGMA busy_timeout=5000",
"PRAGMA foreign_keys=ON",
"PRAGMA synchronous=NORMAL",
}
for _, p := range pragmas {
if _, err := db.Exec(p); err != nil {
return nil, fmt.Errorf("pragma %q: %w", p, err)
}
}
return db, nil
}
Tryb WAL zapewnia równoległe odczyty podczas zapisu — kluczowe dla jednoczesnej pracy API i panelu admina.
Routing i middleware na net/http
Odmowa Gin/Echo/Chi na rzecz http.ServeMux minimalizuje zależności i upraszcza debugowanie:
mux := http.NewServeMux()
// Publiczne API
mux.Handle("/api/leads", rateLimiter(http.HandlerFunc(h.CreateLead)))
mux.Handle("/api/news", http.HandlerFunc(h.ListNews))
// Panel administracyjny
mux.Handle("/admin/", authMiddleware(sessionStore, adminHandler))
Łańcuch middleware: logger → rateLimiter → auth → csrfCheck → handler. Każdy element to niezależna funkcja, przetestowana jednostkowo.
Asynchroniczna integracja z Bitrix24
Wysyłanie zgłoszeń do CRM odbywa się asynchronicznie przez kanały i pulę gorutyn. Brak Redis czy Celery:
type Worker struct {
db *sql.DB
queue chan Job
webhook string
wg sync.WaitGroup
}
func NewWorker(db *sql.DB, webhook string, poolSize int) *Worker {
w := &Worker{
db: db,
queue: make(chan Job, 100),
webhook: webhook,
}
for i := 0; i < poolSize; i++ {
w.wg.Add(1)
go w.process()
}
return w
}
func (w *Worker) Enqueue(job Job) {
select {
case w.queue <- job:
default:
log.Printf("bitrix queue full, lead %d will be retried manually", job.LeadID)
}
}
Bufor 100 zadań, wyłączanie graceful z timeoutem 30 sekund.
Ochrona CSRF za pomocą HMAC-SHA256
Panel administracyjny na formularzach HTML z HTMX chroniony jest tokenami synchronizującymi:
func generateCSRFToken(sessionID, secret string) string {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(sessionID))
return hex.EncodeToString(mac.Sum(nil))
}
func validateCSRFToken(r *http.Request, sessionID, secret string) bool {
token := r.FormValue("csrf_token")
if token == "" {
token = r.Header.Get("X-CSRF-Token")
}
expected := generateCSRFToken(sessionID, secret)
return hmac.Equal([]byte(token), []byte(expected))
}
Token wbudowany w formularze i nagłówki HTMX. hmac.Equal eliminuje ataki czasowe.
Ograniczanie częstotliwości w pamięci
Publiczne API ogranicza 10 żądań/min z IP bez zewnętrznego magazynu:
type IPLimiter struct {
visitors sync.Map
mu sync.Mutex
}
type entry struct {
count int
resetAt time.Time
}
func (l *IPLimiter) Allow(ip string) bool {
now := time.Now()
val, _ := l.visitors.LoadOrStore(ip, &entry{resetAt: now.Add(time.Minute)})
e := val.(*entry)
l.mu.Lock()
defer l.mu.Unlock()
if now.After(e.resetAt) {
e.count = 0
e.resetAt = now.Add(time.Minute)
}
if e.count >= 10 {
return false
}
e.count++
return true
}
sync.Map + cykliczna czyszczenie co 5 minut.
Pierwsze uruchomienie bez konfiguracji
Serwer sam inicjuje bazę danych, generuje hasło administratora i wyświetla je w konsoli. Parametry przez flagi lub zmienne środowiskowe:
./cms— domyślne (port=8080, db=./cms.db)CMS_PORT=8080 CMS_DB_PATH=./data.db ./cms
Zabezpieczenia
| Zagrożenie | Ochrona |
|--------|--------|
| Wstrzykiwanie SQL | Zapytania parametryczne |
| XSS | html/template z automatycznym escapingiem |
| CSRF | Tokeny HMAC |
| Ataki brute force | Ograniczenie 10 żądań/min |
| Przechodzenie po ścieżkach | http.FileServer w sandboxzie |
Co ważne:
- Binarka ~20 MB z statyką przez
embed.FS, zero zależności czasu wykonania - WAL SQLite dla współbieżności bez blokad
- Kanały Go zamiast kolejek Redis/Celery
- HMAC CSRF +
html/templatedla bezpieczeństwa CGO_ENABLED=0— kompilacja krzyżowa z Windows na Linux
— Editorial Team
Brak komentarzy.