Back-End

18 dez, 2018

Trafegando dados entre middleware http usando contexto em Golang

Publicidade

Veja neste artigo 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 próximo middleware e qualquer outra informação que seja coletada em algum dos middlewares que você queira 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 podem se tornar 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 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 adiante 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.