GO Web Services: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
Line 232: Line 232:
When using middleware we need to convert our handler to http.handlers. To do this we use the http.handlerFunc. E.g.
When using middleware we need to convert our handler to http.handlers. To do this we use the http.handlerFunc. E.g.
<syntaxhighlight lang="go">
<syntaxhighlight lang="go">
// Before Middleware
// http.HandleFunc("/products", productsHandler)
// http.HandleFunc("/products/", productHandler)
// http.ListenAndServe(":5000", nil)
func main() {
func main() {
productListHandler := http.HandlerFunc(productsHandler)
productListHandler := http.HandlerFunc(productsHandler)

Revision as of 05:47, 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"
  },
...

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

	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
		}
		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)
}