En aquest tema, aprendrem què és el middleware en el context del desenvolupament web amb Go, com crear i utilitzar middleware, i veurem alguns exemples pràctics. El middleware és una part essencial de moltes aplicacions web, ja que permet la manipulació de sol·licituds i respostes de manera modular i reutilitzable.

Què és el Middleware?

El middleware és un component que s'interposa en el flux de sol·licituds i respostes d'una aplicació web. Permet processar les sol·licituds abans que arribin als controladors finals i manipular les respostes abans que es retornin als clients. Alguns usos comuns del middleware inclouen:

  • Autenticació i autorització
  • Registre de sol·licituds
  • Gestió d'errors
  • Compressió de respostes
  • Caching

Creació de Middleware en Go

En Go, el middleware es pot implementar com una funció que rep un http.Handler i retorna un altre http.Handler. Aquesta funció pot realitzar operacions abans i després de cridar al següent http.Handler en la cadena.

Exemple Bàsic de Middleware

A continuació, es mostra un exemple bàsic de middleware que registra les sol·licituds entrants:

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
)

// Middleware de registre
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        log.Printf("Iniciant sol·licitud %s %s", r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
        log.Printf("Sol·licitud %s %s completada en %s", r.Method, r.URL.Path, time.Since(start))
    })
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Hola, món!")
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", helloHandler)

    // Aplicar el middleware
    loggedMux := loggingMiddleware(mux)

    log.Println("Servidor escoltant en el port 8080")
    http.ListenAndServe(":8080", loggedMux)
}

Explicació del Codi

  1. Funció loggingMiddleware: Aquesta funció rep un http.Handler (next) i retorna un nou http.Handler que registra la sol·licitud abans i després de cridar a next.ServeHTTP(w, r).
  2. Funció helloHandler: Un controlador simple que respon amb "Hola, món!".
  3. Funció main: Es crea un ServeMux per gestionar les rutes, s'aplica el middleware de registre i es comença a escoltar en el port 8080.

Middleware Multiples

És comú utilitzar múltiples middleware en una aplicació. A continuació, es mostra com es poden encadenar diversos middleware:

package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
)

// Middleware de registre
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        log.Printf("Iniciant sol·licitud %s %s", r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
        log.Printf("Sol·licitud %s %s completada en %s", r.Method, r.URL.Path, time.Since(start))
    })
}

// Middleware d'autenticació
func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if r.Header.Get("Authorization") != "Bearer token" {
            http.Error(w, "No autoritzat", http.StatusUnauthorized)
            return
        }
        next.ServeHTTP(w, r)
    })
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Hola, món!")
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", helloHandler)

    // Aplicar múltiples middleware
    handler := loggingMiddleware(authMiddleware(mux))

    log.Println("Servidor escoltant en el port 8080")
    http.ListenAndServe(":8080", handler)
}

Explicació del Codi

  1. Funció authMiddleware: Aquest middleware comprova si la sol·licitud té un encapçalament d'autorització vàlid. Si no és així, retorna un error 401.
  2. Encadenament de Middleware: En la funció main, els middleware es poden encadenar aplicant-los successivament.

Exercicis Pràctics

Exercici 1: Middleware de Compressió

Implementa un middleware que comprimeixi les respostes utilitzant gzip.

Exercici 2: Middleware de Caching

Implementa un middleware que emmagatzemi en memòria cau les respostes per a sol·licituds GET.

Solucions

Solució Exercici 1

package main

import (
    "compress/gzip"
    "fmt"
    "log"
    "net/http"
    "strings"
)

// Middleware de compressió
func gzipMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
            next.ServeHTTP(w, r)
            return
        }

        w.Header().Set("Content-Encoding", "gzip")
        gz := gzip.NewWriter(w)
        defer gz.Close()

        gzrw := gzipResponseWriter{Writer: gz, ResponseWriter: w}
        next.ServeHTTP(gzrw, r)
    })
}

type gzipResponseWriter struct {
    http.ResponseWriter
    Writer *gzip.Writer
}

func (w gzipResponseWriter) Write(b []byte) (int, error) {
    return w.Writer.Write(b)
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Hola, món!")
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", helloHandler)

    // Aplicar el middleware de compressió
    handler := gzipMiddleware(mux)

    log.Println("Servidor escoltant en el port 8080")
    http.ListenAndServe(":8080", handler)
}

Solució Exercici 2

package main

import (
    "fmt"
    "log"
    "net/http"
    "sync"
    "time"
)

// Middleware de caching
func cacheMiddleware(next http.Handler) http.Handler {
    cache := make(map[string]string)
    var mu sync.Mutex

    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if r.Method != http.MethodGet {
            next.ServeHTTP(w, r)
            return
        }

        mu.Lock()
        if response, found := cache[r.URL.Path]; found {
            mu.Unlock()
            fmt.Fprintln(w, response)
            return
        }
        mu.Unlock()

        rw := &responseWriter{ResponseWriter: w}
        next.ServeHTTP(rw, r)

        mu.Lock()
        cache[r.URL.Path] = rw.body
        mu.Unlock()
    })
}

type responseWriter struct {
    http.ResponseWriter
    body string
}

func (rw *responseWriter) Write(b []byte) (int, error) {
    rw.body = string(b)
    return rw.ResponseWriter.Write(b)
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
    time.Sleep(2 * time.Second) // Simular una operació costosa
    fmt.Fprintln(w, "Hola, món!")
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", helloHandler)

    // Aplicar el middleware de caching
    handler := cacheMiddleware(mux)

    log.Println("Servidor escoltant en el port 8080")
    http.ListenAndServe(":8080", handler)
}

Conclusió

El middleware és una eina poderosa per modularitzar i reutilitzar la lògica comuna en les aplicacions web. En aquest tema, hem après què és el middleware, com crear-lo i utilitzar-lo, i hem vist exemples pràctics de middleware de registre, autenticació, compressió i caching. Amb aquests coneixements, estàs preparat per implementar middleware personalitzat en les teves aplicacions Go.

© Copyright 2024. Tots els drets reservats