GO Web Services: Difference between revisions
Line 174: | Line 174: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==POST Method== | ==POST Method== | ||
We use the Unmarshal method to decode the json | We use the Unmarshal method to decode the json. In the source they replaced the ReadAll Unmarshal in favour of json.NewDocder. | ||
<syntaxhighlight lang="go"> | <syntaxhighlight lang="go" highlight="6-15"> | ||
case http.MethodPost: | case http.MethodPost: | ||
// add a new product to the list | // add a new product to the list | ||
var newProduct Product | var newProduct Product | ||
bodyBytes, err := ioutil.ReadAll(r.Body) | bodyBytes, err := ioutil.ReadAll(r.Body) | ||
if err != nil { | if err != nil { | ||
Line 189: | Line 191: | ||
return | return | ||
} | } | ||
err := json.NewDecoder(r.Body).Decode(&product) | |||
if err != nil { | |||
log.Print(err) | |||
w.WriteHeader(http.StatusBadRequest) | |||
return | |||
} | |||
if newProduct.ProductID != 0 { | if newProduct.ProductID != 0 { | ||
w.WriteHeader(http.StatusBadRequest) | w.WriteHeader(http.StatusBadRequest) | ||
Line 197: | Line 208: | ||
w.WriteHeader(http.StatusCreated) | w.WriteHeader(http.StatusCreated) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=Managing the Routes= | =Managing the Routes= | ||
==Example Routes== | ==Example Routes== |
Revision as of 06:21, 26 August 2021
Handling Requests
These are the two main ways to handle requests
- Handle a handler to handle requests matching a pattern
- HandleFunc a function to handle requests matching a pattern
Handle
Here is the signature for Handle.
func Handle(pattern string, handler Handler)
Handler is an interface which has the signature
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
So here is how to use this approach. We create a type and implement the interface required.
// Creating a handler type
type fooHandler struct {
Message string
}
// Implementing the function for the interface
func (f *fooHandler) ServeHTTP(w http.ResponseWrite, r *http.Request) {
w.Write( []byte(f.Message) )
}
func main() {
http.Handle("/foo", &fooHandler{Message: "hello Word"})
}
Remember Remember we can construct a struct using a struct literal e.g.
type Student struct {
Name string
Age int
}
pb := &Student{ // pb == &Student{"Bob", 8}
Name: "Bob",
Age: 8,
}
Requests
These consist of three parts
- Method
- Headers
- Body
HandleFunc
Perhaps somewhat simpler. We just need to create a function with the correct signature
func main() {
foo := funct(w http.ResponseWritter, _ *http.Response) {
w.Write( []byte("hello world") )
}
http.HandleFunc("/foo", foo)
}
Handling JSON
Marshal and Unmarshal
Every GO type implements the empty interface so any struct can be marshaled into JSON.
func Marshal(v interface{}) (byte[]. error)
To ensure the struct is marshaled, all the fields need to be exported. In this example surname would not be marchaled.
type foo struct{
Message string
Age int
surname string
}
json,Marshal(&foo{"4Score",56, "Lincoln"})
To Unmarshal
f := foo{}
json.Unmarshal([]byte(`{"Message":"4Score","Age":56, "Name":"Abe"}`), &f)
Demo Data
Demo Type
Lets make a type to encode
package product
// Product
type Product struct {
ProductID int `json:"productId"`
Manufacturer string `json:"manufacturer"`
Sku string `json:"sku"`
Upc string `json:"upc"`
PricePerUnit string `json:"pricePerUnit"`
QuantityOnHand int `json:"quantityOnHand"`
ProductName string `json:"productName"`
}
Make Some Demo Data
Here is some demo data.
[
{
"productId": 1,
"manufacturer": "Johns-Jenkins",
"sku": "p5z343vdS",
"upc": "939581000000",
"pricePerUnit": "497.45",
"quantityOnHand": 9703,
"productName": "sticky note"
},
{
"productId": 2,
"manufacturer": "Hessel, Schimmel and Feeney",
"sku": "i7v300kmx",
"upc": "740979000000",
"pricePerUnit": "282.29",
"quantityOnHand": 9217,
"productName": "leg warmers"
},
...
Below is a example of how to read the json into a map
func loadProductMap() (map[int]Product, error) {
fileName := "products.json"
_, err := os.Stat(fileName)
if os.IsNotExist(err) {
return nil, fmt.Errorf("file [%s] does not exist", fileName)
}
file, _ := ioutil.ReadFile(fileName)
productList := make([]Product, 0)
err = json.Unmarshal([]byte(file), &productList)
if err != nil {
log.Fatal(err)
}
prodMap := make(map[int]Product)
for i := 0; i < len(productList); i++ {
prodMap[productList[i].ProductID] = productList[i]
}
return prodMap, nil
}
Implementing Method Types
Create Handler and Determine Mathod
We can now provide a GET method within the handler. The handler determines which method by looking at the request.
func handleProduct(w http.ResponseWriter, r *http.Request) {
...
switch r.Method {
// GET
// POST
// etc
}
}
GET Method
We use the Mnmarshal method to encode the json
case http.MethodGet:
productsJSON, err := json.Marshal(productList)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(productsJSON)
POST Method
We use the Unmarshal method to decode the json. In the source they replaced the ReadAll Unmarshal in favour of json.NewDocder.
case http.MethodPost:
// add a new product to the list
var newProduct Product
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
err = json.Unmarshal(bodyBytes, &newProduct)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
err := json.NewDecoder(r.Body).Decode(&product)
if err != nil {
log.Print(err)
w.WriteHeader(http.StatusBadRequest)
return
}
if newProduct.ProductID != 0 {
w.WriteHeader(http.StatusBadRequest)
return
}
newProduct.ProductID = getNextID()
productList = append(productList, newProduct)
w.WriteHeader(http.StatusCreated)
Managing the Routes
Example Routes
With REST we generally have one route for all products and one for a particular product. e.g. GET /products/123. To manage this we greate different handlers.
http.Handle("/products", middlewareHandler(productListHandler))
http.Handle("/products/", middlewareHandler(productItemHandler))
Managing the URL
Here is an example of getting the 123 from the URL. Note we return a 404 if we cannot determine the resource and not a bad request.
func productHandler(w http.ResponseWriter, r *http.Request) {
urlPathSegments := strings.Split(r.URL.Path, "products/")
productID, err := strconv.Atoi(urlPathSegments[len(urlPathSegments)-1])
if err != nil {
log.Print(err)
w.WriteHeader(http.StatusNotFound)
return
}
product, listItemIndex := findProductByID(productID)
if product == nil {
http.Error(w, fmt.Sprintf("no product with id %d", productID), http.StatusNotFound)
return
}
switch r.Method {
case http.MethodGet:
...
Middeware
Explanation
I liked this example provided in GO. Just made sense to me. This is like the interceptors in Angular.
func enableCorsMiddleware(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// do stuff before intended handler here
handler.ServeHttpp(w,r)
// do stuff after intended handler here
})
func intendedFunction(w http.ResponseWriter, r *http.Request) {
// business logic here
}
func main() {
intendedHandler := http.HandlerFunc(intendedFunction)
http.Handle("/foo", enableCorsMiddleware(intendedHandler))
http.ListenAndServe(":5000", nil)
}
Wrapping Handlers
When using middleware we need to convert our handler to http.handlers. To do this we use the http.handlerFunc. E.g.
// Before Middleware
// http.HandleFunc("/products", productsHandler)
// http.HandleFunc("/products/", productHandler)
// http.ListenAndServe(":5000", nil)
func main() {
productListHandler := http.HandlerFunc(productsHandler)
productItemHandler := http.HandlerFunc(productHandler)
http.Handle("/products", middlewareHandler(productListHandler))
http.Handle("/products/", middlewareHandler(productItemHandler))
http.ListenAndServe(":5000", nil)
}
Yet another CORs
Well love diagrams to here are another two. CORs prevents same-site access. Where means different ports, protocols subdomains are not allowed.
Below is a sample function to enable CORs. Do it, do it, don't do it.
func enableCorsMiddleware(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Access-Control-Allow-Origin", "*")
w.Header().Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
w.Header().Add("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Authorization, X-CSRF-Token, Accept-Encoding")
handler.ServeHTTP(w, r)
})
}