From 51520e2238ecc0aaefa686e2fe1f6775e592be32 Mon Sep 17 00:00:00 2001 From: Ruidy Nemausat Date: Tue, 14 Jul 2020 12:55:52 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=95=E2=80=8D=F0=9F=A6=BA=20validate=20?= =?UTF-8?q?body=20using=20middleware?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +- go.mod | 2 + go.sum | 3 +- handlers/products.go | 107 +++++++++++++++++-------------------------- main.go | 17 +++++-- server/server.go | 2 +- 6 files changed, 66 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 5ebdae2..3f9ed8b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ # 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 diff --git a/go.mod b/go.mod index 442ccf7..e2e6b12 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/rjNemo/go-micro go 1.14 + +require github.com/gorilla/mux v1.7.4 diff --git a/go.sum b/go.sum index 7bb54b8..abb0613 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/handlers/products.go b/handlers/products.go index 8909bc5..069d550 100644 --- a/handlers/products.go +++ b/handlers/products.go @@ -1,12 +1,13 @@ package handlers import ( + "context" "fmt" "log" "net/http" - "regexp" "strconv" + "github.com/gorilla/mux" "github.com/rjNemo/go-micro/products/data" ) @@ -18,45 +19,8 @@ type Products struct { // NewProducts creates a Products handler func NewProducts(logger *log.Logger) *Products { return &Products{logger: logger} } -func (p *Products) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // get resources - 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) { +// 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") // fetch products from the datastore 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 -func (p *Products) addProduct(w http.ResponseWriter, r *http.Request) { +// AddProduct reads request body and creates new product +func (p *Products) AddProduct(w http.ResponseWriter, r *http.Request) { p.logger.Println("Handle 'POST' 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 - } + // get product from the request + newProd := r.Context().Value(KeyProduct{}).(*data.Product) // cast into a Product + p.logger.Printf("product: %#v", newProd) data.AddProduct(newProd) } -// updateProduct edit product identified by id -func (p *Products) updateProduct(id int, w http.ResponseWriter, r *http.Request) { - p.logger.Println("Handle 'PUT' 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 - } +// UpdateProduct edit product identified by id +func (p *Products) UpdateProduct(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id, _ := strconv.Atoi(vars["id"]) + + p.logger.Println("Handle 'PUT' request", id) + // get product from the request + newProd := r.Context().Value(KeyProduct{}).(*data.Product) // cast into a Product + p.logger.Printf("product: %#v", newProd) - err = data.UpdateProduct(id, newProd) + err := data.UpdateProduct(id, newProd) if err == data.ErrorProductNotFound { http.Error(w, err.Error(), http.StatusNotFound) return @@ -109,3 +64,27 @@ func (p *Products) updateProduct(id int, w http.ResponseWriter, r *http.Request) 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) + }) +} diff --git a/main.go b/main.go index ebb0fd5..320cec1 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "os/signal" "time" + "github.com/gorilla/mux" "github.com/rjNemo/go-micro/handlers" "github.com/rjNemo/go-micro/server" ) @@ -20,11 +21,21 @@ func main() { // create the handlers productsHandler := handlers.NewProducts(logger) // create a server mux and register the handlers - mux := http.NewServeMux() - mux.Handle("/", productsHandler) + router := mux.NewRouter() + // 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 - srv := server.New(mux, port) + srv := server.New(router, port) // non blocking application server go func() { diff --git a/server/server.go b/server/server.go index 4f88841..e73187c 100644 --- a/server/server.go +++ b/server/server.go @@ -6,7 +6,7 @@ import ( ) // 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{ Addr: port, Handler: mux,