Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

How to use gin

step 1
create file 
-> mkdir todo && cd todo

setup project
-> git init
-> go mod init main.go

import gin
-> import "github.com/gin-gonic/gin"
-> go get -u github.com/gin-gonic/gin

create func main 

use gin follow pattern
-> gin.Default() 
-> r.GET("/", func(c *gin.Context){
    c.JSON(200, gin.H{
        "message":"test",
    })
})
-> r.Run()

--------------------------------
note 
- I delete "" in local.env for make 21, old code ->
DB_CONN="root:my-secret-pw@tcp(127.0.0.1:3306)/myapp?charset-utf8mb4&parseTime=True&loc=Local"

How to do TodoList 

1. create struct in todo.go
-> gorm.model

2. create gorm type for use db
3. create func handler for frontend send
4. create func newTask for get data form path
-> implement handler
-> use gin.Context 
-> var for get data
-> blindJson
-> if err, use c.JSON for set error
    -> should use http and err.Error()

5. database
in todo.go
-> use db.create

in main.go
-> open
-> if err use panic
-> migrate
-> path and method
-> handler
-> use func newTask

6. return in todo.go
-> Create
-> error json
-> statusOK, id

---finish---

7. jwt
-> create directory auth and file auth.go
-> create func AccessToken(put context form gin)
-> jwt.NewWithClaims like this for set ExpiresAt (token time)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, &jwt.StandardClaims{
		ExpiresAt: time.Now().Add(5 * time.Minute).Unix(),
})
-> put license use SignedString(if use string must have []byte(string))
token.SignedString()
-> if have error send statusInternalServerError with JSON message
-> send token to frontend with JSON message

8. call jwt in main.go for give token when call path
-> you can add under r.GET("/)
-> add path for call AccessToken

9. todo.go add check(time and same license) token 
-> create file auth/protect.go for check token
-> create func Protect() paramiter is tokenString return error
-> use jwt.Parse paramiter is tokenString and func (Parse help to validate token)
-> func for check it same jwt.SigningMethodHMSC if not same return nil, error but same return license
func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
		}

		return []byte("==signature=="), nil
})

10. add check token in todo.go func newTask
-> call func Protect but Protect need have token 
-> token can call form context header 
s := c.Request.Header.Get("Authorization")
-> if user don't know token or Bearer token you can use trimPrefix for cut Bearer 
-> fix func Protect when have error must call c.AbortWithStatus(http.StatusUnauthorized) use Abort help to stop, not call any middleware

---- protect before have middleware
package auth

import (
	"fmt"

	jwt "github.com/dgrijalva/jwt-go"
)

func Protect(tokenString string) error {
	// use jwt.Parse ++ in ex i use token insteal _ because i want to know user form token for logging(22)
	_, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		// check it same jwt.SigningMethodHMSC 
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
		}

		return []byte("==signature=="), nil
	})

	return err
}
------

11. use middleware
-> auth/protect adjust code, func Protect paramiter is signature (signature sented from main) 
-> func Protect return gin.HandlerFunc
-> move code in {}
-> edit return err to c.Next() for call other middleware or handler
-> move Request.Header and TrimPrefix in todo.go to auth/protect func Protect
s := c.Request.Header.Get("Authorization")
tokenString := strings.TrimPrefix(s, "Bearer ")
-> edit return of jwt.Parse to return signature, nil
-> check err of jwt.Parse if err, c.AbortWithStatus

12. fix auth.go func AccessToken use higher-order function(have func is argument or return func)
-> take signature is paramiter and return gin.HandlerFunc
-> use other code in {}
-> add return func(c *gin.Context) in all code
-> change ==signature== to signature
-> delete Request.Header, TrimPrefix and check error in todo.go

13. fix main.go use middleware
-> call auth.AccessToken() 
r.GET("/tokenz", auth.AccessToken("==signature==")) 
-> add r.Group
protected := r.Group("", auth.Protect([]byte("==signature==")))
-> use protected insteal r.POST("/todos", handler.NewTask)
protected.POST("/todos", handler.NewTask)

---- AccessToken before have middleware
func AccessToken(c *gin.Context) {
	// jwt.NewWithClaims
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, &jwt.StandardClaims{
		ExpiresAt: time.Now().Add(5 * time.Minute).Unix(),
	})
	// put license use SignedString
	ss, err := token.SignedString([]byte("==signature=="))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": err.Error(),
		})
		return
	}

	// send token to frontend
	c.JSON(http.StatusOK, gin.H{
		"token": ss,
	})
}
------

---- NewTask before have middleware
func (t *TodoHandler) NewTask(c *gin.Context){
	// token can call form context header
	s := c.Request.Header.Get("Authorization")
	// use trimPrefix for cut Bearer
	tokenString := strings.TrimPrefix(s, "Bearer ")

	// call func Protect
	if err := auth.Protect(tokenString); err!= nil {
		c.AbortWithStatus(http.StatusUnauthorized)
		return
	}

	var todo Todo
	if err := c.ShouldBindJSON(&todo); err != nil{
		c.JSON(http.StatusBadRequest, gin.H{
			"message": err.Error(),
		})
		return
	}

	// database
	r := t.db.Create(&todo)
	// return
	if err := r.Error; err != nil{
		c.JSON(http.StatusBadRequest, gin.H{
			"message": err.Error(),
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"ID": todo.Model.ID,
	})
}
------

14. add config with local.env because i want to take signature with config
-> godotenv.Load("local.env")
-> create .gitignore and add local.env and db.test in file
touch .gitignore
-> change ==signature== to os.Getenv(key string)
r.GET("/tokenz", auth.AccessToken(os.Getenv("SIGN")))

15. create file local.env for set value when lazy alway input data ex port=8081 go run main.go
-> touch local.env
-> set value in local.env
PORT=8080
SIGN=mysignature

======= main.go before graceful shutdown ======
package main

import (
	"log"
	"os"

	"github.com/gin-gonic/gin"
	"github.com/joho/godotenv"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"main.go/auth"
	"main.go/todo"
)

func main() {
	err := godotenv.Load("local.env")
	if err != nil {
		log.Printf("please consider environment variables: %s", err)
	}

	// open
	db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
	if err != nil {
		panic("failed to connect database")
	}

	// migrate
	db.AutoMigrate(&todo.Todo{})

	// path and method
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "test",
		})
	})

	r.GET("/tokenz", auth.AccessToken(os.Getenv("SIGN")))
	protected := r.Group("", auth.Protect([]byte(os.Getenv("SIGN"))))

	// handler
	handler := todo.NewHandler(db)
	// use func newTask
	protected.POST("/todos", handler.NewTask)
	r.Run()
}

============
16. make graceful shutdown
-> create instant server
s := &http.Server{
		Addr: ":" + os.Getenv("PORT"),
		Handler: r,
		ReadTimeout: 10 * time.Second,
		WriteTimeout: 10 * time.Second,
		MaxHeaderBytes: 1 << 20,
}
-> use s.ListenAndServe for stop. call in go rutein because wait to handler signal, don't want stop here
go func() {
		if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("listen: %s\n", err)
		}
}()
-> handle signal
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
-> ctx.Done when have signal
<-ctx.Done()
stop()
fmt.Println("shutting down gracefully, press Ctrl+C again to force")

timeoutCtx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
defer cancel()

if err := s.Shutdown(timeoutCtx); err != nil {
	fmt.Println(err)
}
-> delete r.Run
-> test it can work run and crtl + C you can look ^Cshutting down gracefully, press Ctrl+C again to force

17. don't do but note for use in future
idflag for create path output information
-> main.go upper func main create var
var {
    buildcommit = "dev"
    buildtime = time.Now().String()
}
-> create handler /x
r.GET("/x", func(c *gin.Context) {
    c.JSON(200, gin.H{
        "buildcommit": buildcommit,
        "buildtime": buildtime,
    })
})
-> code for build is in local.env or use make build
-> when call path you will give dev and time but you run build buildcommit is commit you will have commit number. you can read more in goodnote

18. don't do but note for use in future
liveness Probe Readiness Probe
-> [liveness Probe] add in func main. create file and delete file
_, err := os.Create("/tmp/live")
if err != nil {
    log.Fatal(err)
}
defer os.Remove("/tmp/live")
-> [Readiness Probe] check set data
-> in code func main we have gorm.Open for set database (it is Readiness Probe)
-> you can create path for test success
r.GET("/healthz", func(c *gin.Context) {
    c.Status(200)
})
-> you can check on kub but you can easy check it on service
cat /tmp/live
echo $? (can see = 0, not see = 1)

19. don't do but note for use in future
rate limit
-> mani.go func main downest
var limiter = rate.NewLimiter(5, 5)

func limiteHanddler(c *gin.Context) {
    if !limiter.Allow() {
        c.AbortWithStatus(http.StatusTooManyRequests)
        return
    }
    c.JSON(200, gin.H{
        "message": "pong",
    })
}
-> create path
r.GET("/limitz", limitedHandler)
-> install vegeta
go install github.com/tsenart/vegeta@latest
-> use vegeta for call path
echo "GET http://:8081/limitz" | vegeta attack -rate=10/s -duration=1s | vegeta report

20. change DB and move to config file
-> open local.env and add DB_CONN="ex.db"
-> main.go change from sqlite.Open("test.db") to
sqlite.Open(os.Getenv("DB_CONN"))
-> I create makeFile you can run for build db maria in docker
make maria
-> fix local.env follow makefile na
DB_CONN="root:my-secret-pw@tcp(127.0.0.1:3306)/myapp?charset-utf8mb4&parseTime=True&loc=Local"
-> main.go gorm.open... change sqlite to mysql
-> go get gorm.io/driver/mysql
-> test by postman and open mysql

21. docker
-> add Dockerfile 
-> add script in Makefile 
image:
	docker build -t todo:test -f Dockerfile .

container:
	docker run  -p:8080:8080 --evn-file ./local.env --link some-mariadb:db \
	--name myapp todo:test
-> run image
make image
-> run container but ตอนแรกเราเรียก local.env โดยตรง แต่ตอนนี้เราเอาไปใช้เรียกใน Dockerfile 
โค้ดนี้อ่ะที่เราไม่ได้เรียก local.env โดยตรง docker run  -p:8080:8080 --evn-file ./local.env
มันจะกลายไปเป็นค่าใน container ทำให้มันเรียก environment ผ่าน os อีกทีแล้วมันไม่รู้จัก "" ต้องลบ "" ใน local.env ออก

22. Logging for return more information
-> if frontend send title = sleep, backend return not Allow. you can add code in todo.go func newtask
-> if you want to know who account have problems, you can beg frontend send hander and make transactionID for show 
position of bug in backend

if todo.Title == "sleep" {
		transactionID := c.Request.Header.Get("TransactionID")
		log.Println(transactionID, "not allowed")
		c.JSON(http.StatusBadRequest, gin.H{
			"error": "not allowed",
		})
		return
}

if you want to know who account have problem you can add some code in auth.go func AccessToken
-> add audience
Audience: "Pungping",
-> รับ token form jwt.Parse in protect.go for get user form token
if claims, ok := token.Claims.(jwt.MapClaims); ok {
			aud := claims["aud"]
			c.Set("aud", aud)
}
if you often to log you can make middleware

23. CORS for ให้ frontend ขอทรัพยากรจาก backend โดยส่งค่าต่างๆที่เอาไว้ตรวจสอบว่ามีสิทธฺิ์มาทาง header
-> create variable config for use cors.DefaultConfig()
-> use AllowOrigins path frontend
-> use AllowHeaders
-> r.Use(cors....)

24. clone frontend project
-> clone
-> run -> GOARCH=wasm GOOS=js go build -o wasm/assets/lib.wasm wasm/main.go
-> cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" . 
you can move it to assets floder

25. frontend not have func list data, we will make Now
-> todo.go create func
-> create struct for receive response
-> find in db
-> check error
-> return status and response
-> add path ใน main.go ด้วย

26. func remove for delete task
-> create func
-> send param
-> delete in db
-> if have error return error
-> return response
-> add root path in main.go 

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages