Golang 入門-Gin框架深入瞭解使用

目錄

框架架構

HTTP 服務器

生命週期

Context

路由

基本路由

路由參數

路由羣組

控制器

數據解析綁定

請求

請求頭

請求參數

Cookies

上傳文件

響應

響應頭

附加Cookie

字符串響應

JSON/XML/YAML響應

視圖響應

文件響應

重定向

同步異步

視圖

傳參

視圖組件

中間件

分類使用方式 

自定義中間件

中間件參數

內置中間件 (簡單認證BasicAuth)

數據庫

Mongodb


框架架構

  • HTTP 服務器

1.默認服務器

router.Run()

2.HTTP 服務器

除了默認服務器中 router.Run() 的方式外,還可以用 http.ListenAndServe(),比如

func main() {
	router := gin.Default()
	http.ListenAndServe(":8080", router)
}

或者自定義 HTTP 服務器的配置:

func main() {
	router := gin.Default()

	s := &http.Server{
		Addr:           ":8080",
		Handler:        router,
		ReadTimeout:    10 * time.Second,
		WriteTimeout:   10 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}
	s.ListenAndServe()
}

3.HTTP 服務器替換方案 想無縫重啓、停機嗎? 以下有幾種方式:

我們可以使用 fvbock/endless 來替換默認的 ListenAndServe。但是 windows 不能使用。

router := gin.Default()
router.GET("/", handler)
// [...]
endless.ListenAndServe(":4242", router)

除了 endless 還可以用manners:

manners兼容windows

manners.ListenAndServe(":8888", r)

如果你使用的 golang 版本大於 1.8 版本, 那麼可以用 http.Server 內置的 Shutdown 方法來實現優雅的關閉服務, 一個簡單的示例代碼如下:

srv := http.Server{
    Addr: ":8080",
    Handler: router,
}

go func() {
    if err :+ srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
        log.Fatalf("listen: %s\n", err)
    }
}

// 其他代碼, 等待關閉信號
...

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
    log.Fatal("Server Shutdown: ", err)
}
log.Println("Server exiting")
  • 生命週期

  • Context

路由

  • 基本路由

gin 框架中採用的路由庫是 httprouter

// 創建帶有默認中間件的路由:
// 日誌與恢復中間件
router := gin.Default()
//創建不帶中間件的路由:
//r := gin.New()

router.GET("/someGet", getting)
router.POST("/somePost", posting)
router.PUT("/somePut", putting)
router.DELETE("/someDelete", deleting)
router.PATCH("/somePatch", patching)
router.HEAD("/someHead", head)
router.OPTIONS("/someOptions", options)
  • 路由參數

api 參數通過Context的Param方法來獲取

router.GET("/string/:name", func(c *gin.Context) {
    	name := c.Param("name")
    	fmt.Println("Hello %s", name)
    })

URL 參數通過 DefaultQuery 或 Query 方法獲取

// url 爲 http://localhost:8080/welcome?name=ningskyer時
// 輸出 Hello ningskyer
// url 爲 http://localhost:8080/welcome時
// 輸出 Hello Guest
router.GET("/welcome", func(c *gin.Context) {
	name := c.DefaultQuery("name", "Guest") //可設置默認值
	// 是 c.Request.URL.Query().Get("lastname") 的簡寫
	lastname := c.Query("lastname") 
	fmt.Println("Hello %s", name)
})

表單參數通過 PostForm 方法獲取

//form
router.POST("/form", func(c *gin.Context) {
	type := c.DefaultPostForm("type", "alert")//可設置默認值
	msg := c.PostForm("msg")
	title := c.PostForm("title")
	fmt.Println("type is %s, msg is %s, title is %s", type, msg, title)
})
  • 路由羣組

someGroup := router.Group("/someGroup"){
    someGroup.GET("/someGet", getting)
    someGroup.POST("/somePost", posting)
}

控制器

  • 數據解析綁定

模型綁定可以將請求體綁定給一個類型,目前支持綁定的類型有 JSON, XML 和標準表單數據 (foo=bar&boo=baz)。 要注意的是綁定時需要給字段設置綁定類型的標籤。比如綁定 JSON 數據時,設置 json:"fieldname"。 使用綁定方法時,Gin 會根據請求頭中 Content-Type 來自動判斷需要解析的類型。如果你明確綁定的類型,你可以不用自動推斷,而用 BindWith 方法。 你也可以指定某字段是必需的。如果一個字段被 binding:"required" 修飾而值卻是空的,請求會失敗並返回錯誤。

// Binding from JSON
type Login struct {
	User     string `form:"user" json:"user" binding:"required"`
	Password string `form:"password" json:"password" binding:"required"`
}

func main() {
	router := gin.Default()

	// 綁定JSON的例子 ({"user": "manu", "password": "123"})
	router.POST("/loginJSON", func(c *gin.Context) {
		var json Login

		if c.BindJSON(&json) == nil {
			if json.User == "manu" && json.Password == "123" {
				c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
			} else {
				c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
			}
		}
	})

	// 綁定普通表單的例子 (user=manu&password=123)
	router.POST("/loginForm", func(c *gin.Context) {
		var form Login
		// 根據請求頭中 content-type 自動推斷.
		if c.Bind(&form) == nil {
			if form.User == "manu" && form.Password == "123" {
				c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
			} else {
				c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
			}
		}
	})
	// 綁定多媒體表單的例子 (user=manu&password=123)
	router.POST("/login", func(c *gin.Context) {
		var form LoginForm
		// 你可以顯式聲明來綁定多媒體表單:
		// c.BindWith(&form, binding.Form)
		// 或者使用自動推斷:
		if c.Bind(&form) == nil {
			if form.User == "user" && form.Password == "password" {
				c.JSON(200, gin.H{"status": "you are logged in"})
			} else {
				c.JSON(401, gin.H{"status": "unauthorized"})
			}
		}
	})
	// Listen and serve on 0.0.0.0:8080
	router.Run(":8080")
}

請求

  • 請求頭

  • 請求參數

  • Cookies

  • 上傳文件

router.POST("/upload", func(c *gin.Context) {
    file, header , err := c.Request.FormFile("upload")
    filename := header.Filename
    fmt.Println(header.Filename)
    out, err := os.Create("./tmp/"+filename+".png")
    if err != nil {
        log.Fatal(err)
    }
    defer out.Close()
    _, err = io.Copy(out, file)
    if err != nil {
        log.Fatal(err)
    }   
})

響應

  • 響應頭

  • 附加Cookie

  • 字符串響應

c.String(http.StatusOK, "some string")
  • JSON/XML/YAML響應

r.GET("/moreJSON", func(c *gin.Context) {
	// You also can use a struct
	var msg struct {
		Name    string `json:"user" xml:"user"`
		Message string
		Number  int
	}
	msg.Name = "Lena"
	msg.Message = "hey"
	msg.Number = 123
	// 注意 msg.Name 變成了 "user" 字段
	// 以下方式都會輸出 :   {"user": "Lena", "Message": "hey", "Number": 123}
	c.JSON(http.StatusOK, gin.H{"user": "Lena", "Message": "hey", "Number": 123})
	c.XML(http.StatusOK, gin.H{"user": "Lena", "Message": "hey", "Number": 123})
	c.YAML(http.StatusOK, gin.H{"user": "Lena", "Message": "hey", "Number": 123})
	c.JSON(http.StatusOK, msg)
	c.XML(http.StatusOK, msg)
	c.YAML(http.StatusOK, msg)
})
  • 視圖響應

先要使用 LoadHTMLTemplates() 方法來加載模板文件

func main() {
	router := gin.Default()
	//加載模板
	router.LoadHTMLGlob("templates/*")
	//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
	//定義路由
	router.GET("/index", func(c *gin.Context) {
		//根據完整文件名渲染模板,並傳遞參數
		c.HTML(http.StatusOK, "index.tmpl", gin.H{
			"title": "Main website",
		})
	})
	router.Run(":8080")
}

模板結構定義

<html>
	<h1>
		{{ .title }}
	</h1>
</html>

不同文件夾下模板名字可以相同,此時需要 LoadHTMLGlob() 加載兩層模板路徑

router.LoadHTMLGlob("templates/**/*")
router.GET("/posts/index", func(c *gin.Context) {
	c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
		"title": "Posts",
	})
	c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
		"title": "Users",
	})
	
}

templates/posts/index.tmpl

<!-- 注意開頭 define 與結尾 end 不可少 -->
{{ define "posts/index.tmpl" }}
<html><h1>
	{{ .title }}
</h1>
</html>
{{ end }}

gin也可以使用自定義的模板引擎,如下

import "html/template"

func main() {
	router := gin.Default()
	html := template.Must(template.ParseFiles("file1", "file2"))
	router.SetHTMLTemplate(html)
	router.Run(":8080")
}
  • 文件響應

//獲取當前文件的相對路徑
router.Static("/assets", "./assets")
//
router.StaticFS("/more_static", http.Dir("my_file_system"))
//獲取相對路徑下的文件
router.StaticFile("/favicon.ico", "./resources/favicon.ico")
  • 重定向

r.GET("/redirect", func(c *gin.Context) {
	//支持內部和外部的重定向
    c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com/")
})
  • 同步異步

goroutine 機制可以方便地實現異步處理

func main() {
	r := gin.Default()
	//1. 異步
	r.GET("/long_async", func(c *gin.Context) {
		// goroutine 中只能使用只讀的上下文 c.Copy()
		cCp := c.Copy()
		go func() {
			time.Sleep(5 * time.Second)

			// 注意使用只讀上下文
			log.Println("Done! in path " + cCp.Request.URL.Path)
		}()
	})
	//2. 同步
	r.GET("/long_sync", func(c *gin.Context) {
		time.Sleep(5 * time.Second)

		// 注意可以使用原始上下文
		log.Println("Done! in path " + c.Request.URL.Path)
	})

	// Listen and serve on 0.0.0.0:8080
	r.Run(":8080")
}

視圖

  • 傳參

  • 視圖組件

中間件

  • 分類使用方式 

// 1.全局中間件
router.Use(gin.Logger())
router.Use(gin.Recovery())

// 2.單路由的中間件,可以加任意多個
router.GET("/benchmark", MyMiddelware(), benchEndpoint)

// 3.羣組路由的中間件
authorized := router.Group("/", MyMiddelware())
// 或者這樣用:
authorized := router.Group("/")
authorized.Use(MyMiddelware())
{
	authorized.POST("/login", loginEndpoint)
}
  • 自定義中間件

//定義
func Logger() gin.HandlerFunc {
	return func(c *gin.Context) {
		t := time.Now()

		// 在gin上下文中定義變量
		c.Set("example", "12345")

		// 請求前

		c.Next()//處理請求

		// 請求後
		latency := time.Since(t)
		log.Print(latency)

		// access the status we are sending
		status := c.Writer.Status()
		log.Println(status)
	}
}
//使用
func main() {
	r := gin.New()
	r.Use(Logger())

	r.GET("/test", func(c *gin.Context) {
		//獲取gin上下文中的變量
		example := c.MustGet("example").(string)

		// 會打印: "12345"
		log.Println(example)
	})

	// 監聽運行於 0.0.0.0:8080
	r.Run(":8080")
}
  • 中間件參數

  • 內置中間件 (簡單認證BasicAuth)

// 模擬私有數據
var secrets = gin.H{
	"foo":    gin.H{"email": "[email protected]", "phone": "123433"},
	"austin": gin.H{"email": "[email protected]", "phone": "666"},
	"lena":   gin.H{"email": "[email protected]", "phone": "523443"},
}

func main() {
	r := gin.Default()

	// 使用 gin.BasicAuth 中間件,設置授權用戶
	authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
		"foo":    "bar",
		"austin": "1234",
		"lena":   "hello2",
		"manu":   "4321",
	}))

	// 定義路由
	authorized.GET("/secrets", func(c *gin.Context) {
		// 獲取提交的用戶名(AuthUserKey)
		user := c.MustGet(gin.AuthUserKey).(string)
		if secret, ok := secrets[user]; ok {
			c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
		} else {
			c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
		}
	})

	// Listen and serve on 0.0.0.0:8080
	r.Run(":8080")
}

數據庫

  • Mongodb

Golang常用的Mongodb驅動爲 mgo.v2,具體可查看文檔

使用方式如下:

//定義 Person 結構,字段須爲首字母大寫
type Person struct {
	Name string
	Phone string
}

router.GET("/mongo", func(context *gin.Context){
	//可本地可遠程,不指定協議時默認爲http協議訪問,此時需要設置 mongodb 的nohttpinterface=false來打開httpinterface。
	//也可以指定mongodb協議,如 "mongodb://127.0.0.1:27017"
	var MOGODB_URI = "127.0.0.1:27017"
	//連接
	session, err := mgo.Dial(MOGODB_URI)
	//連接失敗時終止
	if err != nil {
        panic(err)
    }
	//延遲關閉,釋放資源
	defer session.Close()
	//設置模式
    session.SetMode(mgo.Monotonic, true)
	//選擇數據庫與集合
    c := session.DB("adatabase").C("acollection")
    //插入文檔
    err = c.Insert(&Person{Name:"Ale", Phone:"+55 53 8116 9639"},
               &Person{Name:"Cla",  Phone:"+55 53 8402 8510"})
	//出錯判斷
    if err != nil {
            log.Fatal(err)
    }
	//查詢文檔
    result := Person{}
    //注意mongodb存儲後的字段大小寫問題
    err = c.Find(bson.M{"name": "Ale"}).One(&result)
    //出錯判斷
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Phone:", result.Phone)
})

感謝閱讀 ~ Golang文章會持續更新,一起學習,謝謝

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章