GO Web Services: Difference between revisions
Line 303: | Line 303: | ||
Nothing too difficult here. | Nothing too difficult here. | ||
<syntaxhighlight lang="go"> | <syntaxhighlight lang="go"> | ||
package services | |||
import ( | |||
"database/sql" | |||
"strconv" | |||
) | |||
type DatabaseConnection struct { | |||
Db *sql.DB | |||
} | |||
func NewDatabaseConnection( | |||
dbHost string, | |||
dbPort int, | |||
dbName, dbUser, dbPass string) *DatabaseConnection { | |||
var connectionString = dbUser + ":" + dbPass + "@tcp(" + dbHost + ":" + strconv.Itoa(dbPort) + ")/" + dbName + "?parseTime=true&autocommit=false" | |||
dbConnection, err := sql.Open("mysql", connectionString) | |||
if err != nil { | |||
log.Fatal("Error connecting to Database. Error is %s", err.Error()) | |||
} | |||
repo := DatabaseConnection{ | |||
Db: dbConnection, | |||
} | |||
} | |||
</syntaxhighlight> | </syntaxhighlight> | ||
==Querying Data== | ==Querying Data== |
Revision as of 07:29, 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)
})
}
Persisting Data
Connection to Database
Nothing too difficult here.
package services
import (
"database/sql"
"strconv"
)
type DatabaseConnection struct {
Db *sql.DB
}
func NewDatabaseConnection(
dbHost string,
dbPort int,
dbName, dbUser, dbPass string) *DatabaseConnection {
var connectionString = dbUser + ":" + dbPass + "@tcp(" + dbHost + ":" + strconv.Itoa(dbPort) + ")/" + dbName + "?parseTime=true&autocommit=false"
dbConnection, err := sql.Open("mysql", connectionString)
if err != nil {
log.Fatal("Error connecting to Database. Error is %s", err.Error())
}
repo := DatabaseConnection{
Db: dbConnection,
}
}