Trafegando dados entre middleware http usando contexto em Golang

Veja o vídeo desse arquivo aqui.

Trafegando dados entre middleware

Já vimos como funciona um middleware HTTP e agora vamos ver como passar informação entre os middlewares. Isso é usado por exemplo para passar as credenciais de um usuário para o próximo middleware e qualquer outra informação que seja coletada em algum dos middlewares e você queria passar para frente.

Antes de mais nada vamos fazer um exemplo para mostrar da forma mais clara possível quando os middlewares são executados, isso é muito importante porque erros no entendimento dessa ordem de execução é uma incrível fonte de bugs. No exemplo abaixo colocamos mensagens no terminal toda vez que um middleware é carregado e descarregado. E é sempre bom reforçar isso vai acontecer na mesma ordem em que eles foram registrados.

package main

import (
    "fmt"
    "net/http"

    "github.com/gorilla/mux"
    "github.com/urfave/negroni"
)

func handleMain(w http.ResponseWriter, r *http.Request) {
    _, err := w.Write([]byte("{\"value\":42}\n"))
    if err != nil {
        fmt.Println("error handleMain", err)
    }
}

func middleware1() negroni.Handler {
    fmt.Println("carregando middleware 1")
    return negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
        fmt.Println("empilhando middleware 1")
        next(w, r)
        fmt.Println("desempilhando middleware 1")
    })
}

func middleware2() negroni.Handler {
    fmt.Println("carregando middleware 2")
    return negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
        fmt.Println("empilhando middleware 2")
        next(w, r)
        fmt.Println("desempilhando middleware 2")
    })
}

func middleware3() negroni.Handler {
    fmt.Println("carregando middleware 3")
    return negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
        fmt.Println("empilhando middleware 3")
        next(w, r)
        fmt.Println("desempilhando middleware 3")
    })
}

func main() {
    n := negroni.Classic()
    n.Use(middleware1())
    n.Use(middleware2())
    n.Use(middleware3())
    fmt.Println("-=-=-=-=-=-=-=-=-=-=-=-=-=-")
    r := mux.NewRouter().StrictSlash(true)
    n.UseHandler(r)

    r.HandleFunc("/", handleMain).Methods("GET")

    fmt.Println("main listen at :8080")
    err := http.ListenAndServe(":8080", n)
    if err != nil {
        fmt.Println(err)
    }
}

A maneira canônica de passar informações a diante durante o processamento de uma requisição HTTP é usando contexto. Veja o exemplo.

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "net/http"

    "github.com/gorilla/mux"
    "github.com/urfave/negroni"
)

type key int

const (
    dataKey key = iota
)

type data struct {
    ValueA string `json:"value_a"`
    ValueB int    `json:"value_b"`
}

func setContextData(r *http.Request, d *data) (ro *http.Request) {
    ctx := r.Context()
    ctx = context.WithValue(ctx, dataKey, d)
    ro = r.WithContext(ctx)
    return
}

func getContextData(r *http.Request) (d data) {
    d = *r.Context().Value(dataKey).(*data)
    return
}

func middleware1() negroni.Handler {
    return negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
        d := data{
            ValueA: "valor A",
            ValueB: 42,
        }
        r = setContextData(r, &d)
        next(w, r)
    })
}

func middleware2() negroni.Handler {
    return negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
        d := getContextData(r)
        d.ValueA += "A"
        r = setContextData(r, &d)
        next(w, r)
    })
}

func middleware3() negroni.Handler {
    return negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
        d := getContextData(r)
        d.ValueA += "A"
        r = setContextData(r, &d)
        next(w, r)
    })
}

func handleMain(w http.ResponseWriter, r *http.Request) {
    d := getContextData(r)
    j, err := json.MarshalIndent(d, "", "\t")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    w.Header().Set("Content-Type", "application/json")
    _, err = w.Write(j)
    if err != nil {
        fmt.Println(err)
    }
}

func main() {
    n := negroni.Classic()
    n.Use(middleware1())
    n.Use(middleware2())
    n.Use(middleware3())

    r := mux.NewRouter().StrictSlash(true)
    n.UseHandler(r)

    r.HandleFunc("/", handleMain).Methods("GET")

    fmt.Println("main listen at :8080")
    err := http.ListenAndServe(":8080", n)
    if err != nil {
        fmt.Println(err)
    }
}

Tem muito mais exemplos no repositório do nosso grupo de estudos de Go, vale a pena dar uma conferida e também colaborar, estamos sempre precisando de ajuda para ter uma material completo e atualizado.

Cesar Gimenes

Última modificação