CMS tête-bêche minimaliste en Go pur : architecture et mise en œuvre
Le choix clé réside dans l'utilisation de modernc.org/sqlite au lieu de mattn/go-sqlite3. Il s'agit d'une portage pur Go de SQLite avec une API compatible database/sql, sans nécessiter de compilateur 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
}
// Mode WAL pour un accès concurrent
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
}
Le mode WAL permet la lecture parallèle pendant les écritures — essentiel pour faire fonctionner simultanément l’API et le panneau d’administration.
Routage et middleware avec net/http
En optant pour http.ServeMux plutôt que Gin/Echo/Chi, on réduit les dépendances et simplifie le débogage :
mux := http.NewServeMux()
// API publique
mux.Handle("/api/leads", rateLimiter(http.HandlerFunc(h.CreateLead)))
mux.Handle("/api/news", http.HandlerFunc(h.ListNews))
// Panneau d'administration
mux.Handle("/admin/", authMiddleware(sessionStore, adminHandler))
Chaîne de middleware : logger → rateLimiter → auth → csrfCheck → handler. Chaque composant est une fonction indépendante, couverte par des tests.
Intégration asynchrone avec Bitrix24
Les soumissions de leads vers le CRM sont gérées de manière asynchrone via des canaux Go et un pool de travailleurs — pas besoin de Redis ou 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("file d'attente bitrix pleine, lead %d sera retry manuellement", job.LeadID)
}
}
Une file de 100 tâches, arrêt propre avec timeout de 30 secondes.
Protection CSRF avec HMAC-SHA256
Les formulaires administrateurs utilisant HTMX sont protégés par des jetons synchronisés :
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))
}
Les jetons sont intégrés dans les formulaires et les en-têtes HTMX. hmac.Equal empêche les attaques par timing.
Limitation de débit en mémoire vive
L’API publique limite les requêtes à 10 par minute par IP, sans stockage externe :
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 avec nettoyage toutes les 5 minutes.
Première exécution sans configuration
Le serveur initialise automatiquement la base de données, génère un mot de passe administrateur et l'affiche dans la console. La configuration se fait via des flags ou variables d’environnement :
./cms— valeurs par défaut (port=8080, db=./cms.db)CMS_PORT=8080 CMS_DB_PATH=./data.db ./cms
Mesures de sécurité
| Menace | Solution |
|--------|----------|
| Injection SQL | Requêtes paramétrées |
| XSS | html/template avec échappement automatique |
| CSRF | Jetons HMAC |
| Attaque par force brute | Limiteur à 10 requêtes/min |
| Navigation de fichiers | http.FileServer en sandbox |
Points forts :
- Binaire ~20 Mo avec ressources statiques embarquées via
embed.FS, aucune dépendance au runtime - SQLite en mode WAL pour la concurrence sans verrous
- Canaux Go au lieu de files Redis/Celery
- Sécurité robuste avec CSRF HMAC +
html/template CGO_ENABLED=0— compilation croisée depuis Windows vers Linux
— Editorial Team
Aucun commentaire pour le moment.