🐕‍🦺 validate body using middleware

This commit is contained in:
Ruidy Nemausat 2020-07-14 12:55:52 +02:00
parent 3318c90370
commit 51520e2238
6 changed files with 66 additions and 70 deletions

View file

@ -1,3 +1,6 @@
# Coffee Shop Microservice # Coffee Shop Microservice
- Built with `Golang` ## Built with
- [Golang](https://golang.org/) — open source programming language that makes it easy to build simple, reliable, and efficient software
- [Gorilla](https://www.gorillatoolkit.org/) — The Golang web toolkit

2
go.mod
View file

@ -1,3 +1,5 @@
module github.com/rjNemo/go-micro module github.com/rjNemo/go-micro
go 1.14 go 1.14
require github.com/gorilla/mux v1.7.4

3
go.sum
View file

@ -1 +1,2 @@
golang.org/x/tools v0.0.0-20200713195033-f8240f79c3d3 h1:xV8QVipADSbgfDrrjnUyOJILDkpbFpyoLV4x06POJ7I= github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=

View file

@ -1,12 +1,13 @@
package handlers package handlers
import ( import (
"context"
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"regexp"
"strconv" "strconv"
"github.com/gorilla/mux"
"github.com/rjNemo/go-micro/products/data" "github.com/rjNemo/go-micro/products/data"
) )
@ -18,45 +19,8 @@ type Products struct {
// NewProducts creates a Products handler // NewProducts creates a Products handler
func NewProducts(logger *log.Logger) *Products { return &Products{logger: logger} } func NewProducts(logger *log.Logger) *Products { return &Products{logger: logger} }
func (p *Products) ServeHTTP(w http.ResponseWriter, r *http.Request) { // GetProducts writes all products to response in JSON format
// get resources func (p *Products) GetProducts(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
p.getProducts(w, r)
return
}
// create one resource
if r.Method == http.MethodPost {
p.addProduct(w, r)
return
}
// update one resource
if r.Method == http.MethodPut {
// look for ID in the URI using regexp
regx := regexp.MustCompile(`/([0-9]+)`)
group := regx.FindAllStringSubmatch(r.URL.Path, -1)
if len(group) != 1 {
http.Error(w, "Invalid URI", http.StatusBadRequest)
return
}
if len(group[0]) != 2 {
http.Error(w, "Invalid URI", http.StatusBadRequest)
return
}
idString := group[0][1]
id, _ := strconv.Atoi(idString)
p.logger.Printf("Got ID: %d", id)
p.updateProduct(id, w, r)
return
}
// catch all other HTTP requests
w.WriteHeader(http.StatusMethodNotAllowed)
}
// getProducts writes all products to response in JSON format
func (p *Products) getProducts(w http.ResponseWriter, r *http.Request) {
p.logger.Println("Handle 'GET' request") p.logger.Println("Handle 'GET' request")
// fetch products from the datastore // fetch products from the datastore
productList := data.AllProducts() productList := data.AllProducts()
@ -69,36 +33,27 @@ func (p *Products) getProducts(w http.ResponseWriter, r *http.Request) {
} }
} }
// addProduct reads request body and creates new product // AddProduct reads request body and creates new product
func (p *Products) addProduct(w http.ResponseWriter, r *http.Request) { func (p *Products) AddProduct(w http.ResponseWriter, r *http.Request) {
p.logger.Println("Handle 'POST' request") p.logger.Println("Handle 'POST' request")
// create a new product // get product from the request
newProd := &data.Product{} newProd := r.Context().Value(KeyProduct{}).(*data.Product) // cast into a Product
// deserialize JSON to product
err := newProd.FromJSON(r.Body)
if err != nil {
errMsg := fmt.Sprintf("Unable to decode data: %s\n", err)
http.Error(w, errMsg, http.StatusBadRequest)
return
}
p.logger.Printf("product: %#v", newProd) p.logger.Printf("product: %#v", newProd)
data.AddProduct(newProd) data.AddProduct(newProd)
} }
// updateProduct edit product identified by id // UpdateProduct edit product identified by id
func (p *Products) updateProduct(id int, w http.ResponseWriter, r *http.Request) { func (p *Products) UpdateProduct(w http.ResponseWriter, r *http.Request) {
p.logger.Println("Handle 'PUT' request") vars := mux.Vars(r)
// create a new product id, _ := strconv.Atoi(vars["id"])
newProd := &data.Product{}
// deserialize JSON to product p.logger.Println("Handle 'PUT' request", id)
err := newProd.FromJSON(r.Body) // get product from the request
if err != nil { newProd := r.Context().Value(KeyProduct{}).(*data.Product) // cast into a Product
errMsg := fmt.Sprintf("Unable to decode data: %s\n", err)
http.Error(w, errMsg, http.StatusBadRequest)
return
}
p.logger.Printf("product: %#v", newProd) p.logger.Printf("product: %#v", newProd)
err = data.UpdateProduct(id, newProd) err := data.UpdateProduct(id, newProd)
if err == data.ErrorProductNotFound { if err == data.ErrorProductNotFound {
http.Error(w, err.Error(), http.StatusNotFound) http.Error(w, err.Error(), http.StatusNotFound)
return return
@ -109,3 +64,27 @@ func (p *Products) updateProduct(id int, w http.ResponseWriter, r *http.Request)
return return
} }
} }
// KeyProduct is a key used to pass validated product to handler
type KeyProduct struct{}
// ProductValidationMiddleware validates the data passed by the user
func (p *Products) ProductValidationMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// create a new product
newProd := &data.Product{}
// deserialize JSON to product
err := newProd.FromJSON(r.Body)
if err != nil {
errMsg := fmt.Sprintf("Unable to decode data: %s\n", err)
http.Error(w, errMsg, http.StatusBadRequest)
return
}
// add product to the context
ctx := context.WithValue(r.Context(), KeyProduct{}, newProd)
req := r.WithContext(ctx)
// call the next handler
next.ServeHTTP(w, req)
})
}

17
main.go
View file

@ -8,6 +8,7 @@ import (
"os/signal" "os/signal"
"time" "time"
"github.com/gorilla/mux"
"github.com/rjNemo/go-micro/handlers" "github.com/rjNemo/go-micro/handlers"
"github.com/rjNemo/go-micro/server" "github.com/rjNemo/go-micro/server"
) )
@ -20,11 +21,21 @@ func main() {
// create the handlers // create the handlers
productsHandler := handlers.NewProducts(logger) productsHandler := handlers.NewProducts(logger)
// create a server mux and register the handlers // create a server mux and register the handlers
mux := http.NewServeMux() router := mux.NewRouter()
mux.Handle("/", productsHandler) // GET
getRouter := router.Methods(http.MethodGet).Subrouter()
getRouter.HandleFunc("/", productsHandler.GetProducts)
// POST
postRouter := router.Methods(http.MethodPost).Subrouter()
postRouter.HandleFunc("/", productsHandler.AddProduct)
postRouter.Use(productsHandler.ProductValidationMiddleware)
// PUT
putRouter := router.Methods(http.MethodPut).Subrouter()
putRouter.HandleFunc("/{id:[0-9]+}", productsHandler.UpdateProduct)
putRouter.Use(productsHandler.ProductValidationMiddleware)
// creates a new server // creates a new server
srv := server.New(mux, port) srv := server.New(router, port)
// non blocking application server // non blocking application server
go func() { go func() {

View file

@ -6,7 +6,7 @@ import (
) )
// New creates a server using given mux and port // New creates a server using given mux and port
func New(mux *http.ServeMux, port string) *http.Server { func New(mux http.Handler, port string) *http.Server {
return &http.Server{ return &http.Server{
Addr: port, Addr: port,
Handler: mux, Handler: mux,