Trafegando dados entre middleware http usando contexto em Golang

Como trocar informações entre os middlewares http do seu sistema usando contexto.

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 proximo 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.

Nossos encontros ocorrem todas as quintas-feiras ás 22h00, para participar entre no canal de Go no slack e procure por #brazil

| | | | | |