Cod în jurul fiecărui handler
Middleware este cod care rulează în jurul handlerelor tale: înainte ca cererea să ajungă la ele, după ce răspunsul pleacă de la ele sau în locul lor. Jurnalizare, autentificare, limitarea numărului de cereri, comprimare — toate sunt middleware.
Aceeași idee circulă sub trei nume: framework-urile web spun middleware, gRPC spune interceptor, iar unele ecosisteme spun filtru. Un singur concept, trei vocabulare.
În Go, un middleware este o funcție care primește un handler și returnează un handler împachetat — exact șablonul Decorator din grupul șabloanelor de proiectare, iar o stivă de astfel de funcții se comportă ca un Chain of Responsibility.
Un middleware de jurnalizare
withLog împachetează orice handler: afișează cererea care intră, lasă handlerul să lucreze și afișează din nou la ieșire. Handlerul în sine rămâne complet neștiutor.
Împachetezi o dată, jurnalizezi tot
Handlerul împachetat servește două căi diferite și ambele sunt jurnalizate — jurnalizarea este scrisă o singură dată, în împachetare, nu în fiecare handler.
Ieșire: program Go și rezultatul capturat
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
)
// Middleware: primește un handler și returnează un handler împachetat.
func withLog(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println("-> ", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
fmt.Println("<- gata")
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /users", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Ana, Ben")
})
mux.HandleFunc("GET /notes", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "cumpărături, idei")
})
server := httptest.NewServer(withLog(mux))
defer server.Close()
for _, path := range []string{"/users", "/notes"} {
resp, _ := http.Get(server.URL + path)
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
fmt.Println("corp:", strings.TrimSpace(string(body)))
}
}
Middleware care oprește cererea
Un middleware poate și să refuze: o verificare de autentificare care nu găsește o cheie validă scrie 401 Unauthorized și nu apelează niciodată handlerul următor.
Înlănțuite ca withLog(withAuth(handler)), cererea trece prin straturi în ordine — iar răspunsul iese înapoi prin ele.
Un strat de autentificare în lanț
Cererea fără cheie este oprită de stratul de autentificare cu 401 — handlerul nu rulează niciodată. Cu cheie, cererea trece prin tot lanțul și ajunge la date.
Ieșire: program Go și rezultatul capturat
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
)
func withLog(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println("->", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}
// Middleware de autentificare: poate opri cererea înainte de handler.
func withAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("X-API-Key") != "secret123" {
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprintln(w, "cheie API lipsă sau greșită")
return
}
next.ServeHTTP(w, r)
})
}
func main() {
data := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "raportul secret")
})
server := httptest.NewServer(withLog(withAuth(data)))
defer server.Close()
client := &http.Client{}
for _, key := range []string{"", "secret123"} {
req, _ := http.NewRequest(http.MethodGet, server.URL+"/report", nil)
if key != "" {
req.Header.Set("X-API-Key", key)
}
resp, _ := client.Do(req)
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
fmt.Println(" status:", resp.StatusCode, "-", strings.TrimSpace(string(body)))
}
}