Menu
Coddy logo textTech

Middleware come Decorator

Fa parte della sezione Programmazione orientata agli oggetti del percorso GO di Coddy. Lezione 97 di 107.

Middleware è una delle applicazioni più pratiche del pattern Decorator in Go. Nello sviluppo web, le funzioni middleware avvolgono gli handler HTTP per aggiungere funzionalità trasversali come la registrazione dei log, l'autenticazione o la misurazione dei tempi, senza modificare l'handler originale.

L'intuizione chiave è che il middleware prende un handler e restituisce un nuovo handler con un comportamento aggiunto. Questo segue esattamente il pattern Decorator: la stessa interfaccia in ingresso e in uscita, con funzionalità potenziate:

type Handler func(request string) string

func LoggingMiddleware(next Handler) Handler {
    return func(request string) string {
        result := next(request)
        return "[LOG] " + result
    }
}

func AuthMiddleware(next Handler) Handler {
    return func(request string) string {
        return "[AUTH] " + next(request)
    }
}

Ogni middleware racchiude il gestore successivo nella catena, aggiungendo il proprio comportamento prima o dopo aver chiamato il gestore racchiuso. Puoi impilare più middleware componendoli:

func MainHandler(request string) string {
    return "Response to: " + request
}

handler := LoggingMiddleware(AuthMiddleware(MainHandler))
fmt.Println(handler("GET /users"))
// [LOG] [AUTH] Response to: GET /users

L'ordine dei wrapper determina l'ordine di esecuzione: il middleware più esterno viene eseguito per primo. Questo schema è ampiamente utilizzato nei framework web Go per le pipeline di elaborazione delle richieste, rendendo facile aggiungere o rimuovere funzionalità senza modificare la logica principale dell'handler.

challenge icon

Sfida

Facile

Creiamo una pipeline di elaborazione delle richieste usando il pattern Middleware come Decorator! Creerai un sistema in cui le funzioni middleware avvolgono gli handler per aggiungere funzionalità come il rilevamento dei tempi, la convalida e la formattazione, tutto senza modificare la logica dell'handler principale.

Organizzerai il codice in tre file:

  • handler.go: definisci il tipo di handler e l'handler di base che elabora le richieste.

    Crea un tipo Handler come funzione che accetta una stringa di richiesta e restituisce una stringa di risposta:

    type Handler func(request string) string

    Implementa una funzione BaseHandler che restituisce Processed: [request] per qualsiasi richiesta ricevuta.

  • middleware.go: crea le funzioni middleware che avvolgono gli handler con comportamenti aggiuntivi.

    Crea tre funzioni middleware, ognuna delle quali accetta un Handler e restituisce un nuovo Handler:

    • TimingMiddleware: avvolge la risposta con informazioni sui tempi e restituisce [TIMING] [response]
    • ValidationMiddleware: aggiunge un prefisso di convalida e restituisce [VALID] [response]
    • UppercaseMiddleware: converte l'intera risposta in maiuscolo

    Ogni middleware deve chiamare l'handler avvolto e arricchirne il risultato.

  • main.go: crea una pipeline di elaborazione componendo i middleware.

    Leggi il numero di livelli middleware da applicare. Poi, per ogni livello, leggi il tipo di middleware (timing, validation o uppercase) e avvolgi l'handler corrente con quel middleware. Applicali nell'ordine in cui vengono letti: il primo middleware letto diventa l'involucro più esterno.

    Dopo aver creato la pipeline, leggi la stringa della richiesta, elaborala tramite l'handler composto e stampa il risultato.

Verranno forniti i seguenti input:

  • Riga 1: numero di livelli middleware
  • Righe successive: un tipo di middleware per riga (timing, validation o uppercase)
  • Ultima riga: la richiesta da elaborare

Ad esempio, dato:

2
timing
validation
GET /users

L'output dovrebbe essere:

[TIMING] [VALID] Processed: GET /users

E dato:

3
uppercase
timing
validation
POST /data

L'output dovrebbe essere:

[TIMING] [VALID] PROCESSED: POST /DATA

E dato:

1
uppercase
hello world

L'output dovrebbe essere:

PROCESSED: HELLO WORLD

E dato:

0
simple request

L'output dovrebbe essere:

Processed: simple request

Nota come l'ordine in cui vengono avvolti i middleware influenzi l'output: il primo middleware applicato (quello più esterno) viene eseguito per primo e riceve il risultato finale di tutti i middleware interni. Nel secondo esempio, il middleware maiuscolo viene eseguito per ultimo (è quello più interno), quindi converte in maiuscolo la risposta di base prima che timing e validation aggiungano i loro prefissi!

Provalo tu

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Leggi il numero di livelli di middleware
	nLine, _ := reader.ReadString('\n')
	n, _ := strconv.Atoi(strings.TrimSpace(nLine))

	// Leggi i tipi di middleware
	middlewareTypes := make([]string, n)
	for i := 0; i < n; i++ {
		line, _ := reader.ReadString('\n')
		middlewareTypes[i] = strings.TrimSpace(line)
	}

	// Leggi la richiesta
	request, _ := reader.ReadString('\n')
	request = strings.TrimSpace(request)

	// TODO: Inizia con BaseHandler

	// TODO: Costruisci la pipeline avvolgendo l'handler con i middleware
	// Applica i middleware nell'ordine indicato: il primo letto diventa il livello più esterno
	// Suggerimento: scorri middlewareTypes in ordine INVERSO per ottenere questo risultato
	// L'ultimo middleware applicato sarà il più esterno (verrà eseguito per primo)

	// TODO: Elabora la richiesta tramite l'handler composto e stampa il risultato
	fmt.Println("TODO: implement pipeline")
}
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore Go online