🐕‍🦺 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
- 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
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
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)
})
}

17
main.go
View file

@ -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() {

View file

@ -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,