Golang GinWeb框架8-重定向/自定義中間件/認(rèn)證/HTTPS支持/優(yōu)雅重啟等
簡介
本文接著上文(Golang GinWeb框架7-靜態(tài)文件/模板渲染)繼續(xù)探索GinWeb框架.
重定向
Gin返回一個(gè)HTTP重定向非常簡單, 使用Redirect方法即可. 內(nèi)部和外部鏈接都支持.
- package main
 - import (
 - "github.com/gin-gonic/gin"
 - "net/http"
 - )
 - func main() {
 - r := gin.Default()
 - r.GET("/test", func(c *gin.Context) {
 - c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") //重定向到外部鏈接
 - })
 - //重定向到內(nèi)部鏈接
 - r.GET("/internal", func(c *gin.Context) {
 - c.Redirect(http.StatusMovedPermanently, "/home")
 - })
 - r.GET("/home", func(c *gin.Context) {
 - c.JSON(200, gin.H{"msg": "這是首頁"})
 - })
 - r.Run(":8080")
 - }
 - /*
 - 重定向到外部鏈接,訪問:http://localhost:8080/test
 - 重定向到內(nèi)部鏈接,訪問:http://localhost:8080/internal
 - */
 
從POST方法中完成HTTP重定向, 參考問題#444 https://github.com/gin-gonic/gin/issues/444
- r.POST("/test", func(c *gin.Context) {
 - c.Redirect(http.StatusFound, "/foo")
 - })
 
如果要產(chǎn)生一個(gè)路由重定向, 類似上面的內(nèi)部重定向, 則使用 HandleContext方法, 像下面這樣使用:
- r.GET("/test", func(c *gin.Context) {
 - c.Request.URL.Path = "/test2"
 - r.HandleContext(c)
 - })
 - r.GET("/test2", func(c *gin.Context) {
 - c.JSON(200, gin.H{"hello": "world"})
 - })
 
自定義中間件
- package main
 - import (
 - "github.com/gin-gonic/gin"
 - "log"
 - "time"
 - )
 - //自定義日志中間件
 - func Logger() gin.HandlerFunc {
 - return func(c *gin.Context) {
 - t := time.Now()
 - // Set example variable 在gin上下文中設(shè)置鍵值對(duì)
 - c.Set("example", "12345")
 - // before request
 - //Next方法只能用于中間件中,在當(dāng)前中間件中, 從方法鏈執(zhí)行掛起的處理器
 - c.Next()
 - // after request 打印中間件執(zhí)行耗時(shí)
 - latency := time.Since(t)
 - log.Print(latency)
 - // access the status we are sending 打印本中間件的狀態(tài)碼
 - status := c.Writer.Status()
 - log.Println(status)
 - }
 - }
 - func main() {
 - r := gin.New()
 - //使用該自定義中間件
 - r.Use(Logger())
 - r.GET("/test", func(c *gin.Context) {
 - example := c.MustGet("example").(string) //從上下文中獲取鍵值對(duì)
 - // it would print: "12345"
 - log.Println(example)
 - })
 - // Listen and serve on 0.0.0.0:8080
 - r.Run(":8080")
 - }
 
使用基本認(rèn)證BasicAuth()中間件
- package main
 - import (
 - "github.com/gin-gonic/gin"
 - "net/http"
 - )
 - // simulate some private data
 - var secrets = gin.H{
 - "foo": gin.H{"email": "foo@bar.com", "phone": "123433"},
 - "austin": gin.H{"email": "austin@example.com", "phone": "666"},
 - "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"},
 - }
 - func main() {
 - r := gin.Default()
 - // Group using gin.BasicAuth() middleware
 - // gin.Accounts is a shortcut for map[string]string
 - // 路由組authorized使用基本認(rèn)證中間件, 參數(shù)為gin.Accounts,是一個(gè)map,鍵名是用戶名, 鍵值是密碼, 該中間件會(huì)將認(rèn)證信息保存到cookie中
 - authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
 - "foo": "bar",
 - "austin": "1234",
 - "lena": "hello2",
 - "manu": "4321",
 - }))
 - // /admin/secrets endpoint
 - // hit "localhost:8080/admin/secrets
 - authorized.GET("/secrets", func(c *gin.Context) {
 - // get user, it was set by the BasicAuth middleware
 - // 從cookie中獲取用戶認(rèn)證信息, 鍵名為user
 - 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")
 - }
 - /*
 - 測試訪問:http://localhost:8080/admin/secrets
 - */
 
在中間件中使用協(xié)程Goroutines
在中間件或者控制器中啟動(dòng)新協(xié)程時(shí), 不能直接使用原來的Gin上下文, 必須使用一個(gè)只讀的上下文副本
- package main
 - import (
 - "github.com/gin-gonic/gin"
 - "log"
 - "time"
 - )
 - func main() {
 - r := gin.Default()
 - r.GET("/long_async", func(c *gin.Context) {
 - // create copy to be used inside the goroutine
 - // 創(chuàng)建一個(gè)Gin上下文的副本, 準(zhǔn)備在協(xié)程Goroutine中使用
 - cCp := c.Copy()
 - go func() {
 - // simulate a long task with time.Sleep(). 5 seconds
 - // 模擬長時(shí)間任務(wù),這里是5秒
 - time.Sleep(5 * time.Second)
 - // note that you are using the copied context "cCp", IMPORTANT
 - // 在中間件或者控制器中啟動(dòng)協(xié)程時(shí), 不能直接使用原來的上下文, 必須使用一個(gè)只讀的上線文副本
 - log.Println("Done! in path " + cCp.Request.URL.Path)
 - }()
 - })
 - r.GET("/long_sync", func(c *gin.Context) {
 - // simulate a long task with time.Sleep(). 5 seconds
 - time.Sleep(5 * time.Second)
 - // since we are NOT using a goroutine, we do not have to copy the context
 - // 沒有使用協(xié)程時(shí), 可以直接使用Gin上下文
 - log.Println("Done! in path " + c.Request.URL.Path)
 - })
 - // Listen and serve on 0.0.0.0:8080
 - r.Run(":8080")
 - }
 - /*
 - 模擬同步阻塞訪問:http://localhost:8080/long_sync
 - 模擬異步非阻塞訪問:http://localhost:8080/long_async
 - */
 
自定義HTTP配置
直接使用 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()
 - }
 
支持Let'sEncrypt證書加密處理HTTPS
下面是一行式的LetsEncrypt HTTPS服務(wù)
- package main
 - import (
 - "log"
 - "github.com/gin-gonic/autotls"
 - "github.com/gin-gonic/gin"
 - )
 - func main() {
 - r := gin.Default()
 - // Ping handler
 - r.GET("/ping", func(c *gin.Context) {
 - c.String(200, "pong")
 - })
 - //一行式LetsEncrypt證書, 處理https
 - log.Fatal(autotls.Run(r, "example1.com", "example2.com"))
 - }
 
自定義自動(dòng)證書管理器autocert manager實(shí)例代碼:
- package main
 - import (
 - "log"
 - "github.com/gin-gonic/autotls"
 - "github.com/gin-gonic/gin"
 - "golang.org/x/crypto/acme/autocert"
 - )
 - func main() {
 - r := gin.Default()
 - // Ping handler
 - r.GET("/ping", func(c *gin.Context) {
 - c.String(200, "pong")
 - })
 - m := autocert.Manager{
 - Prompt: autocert.AcceptTOS, //Prompt指定一個(gè)回調(diào)函數(shù)有條件的接受證書機(jī)構(gòu)CA的TOS服務(wù), 使用AcceptTOS總是接受服務(wù)條款
 - HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"), //HostPolicy用于控制指定哪些域名, 管理器將檢索新證書
 - Cache: autocert.DirCache("/var/www/.cache"), //緩存證書和其他狀態(tài)
 - }
 - log.Fatal(autotls.RunWithManager(r, &m))
 - }
 
詳情參考autotls包
使用Gin運(yùn)行多個(gè)服務(wù)
可以在主函數(shù)中使用協(xié)程Goroutine運(yùn)行多個(gè)服務(wù), 每個(gè)服務(wù)端口不同, 路由分組也不同. 請參考這個(gè)問題, 嘗試運(yùn)行以下示例代碼:
- package main
 - import (
 - "log"
 - "net/http"
 - "time"
 - "github.com/gin-gonic/gin"
 - "golang.org/x/sync/errgroup"
 - )
 - var (
 - g errgroup.Group
 - )
 - func router01() http.Handler {
 - e := gin.New()
 - e.Use(gin.Recovery())
 - e.GET("/", func(c *gin.Context) {
 - c.JSON(
 - http.StatusOK,
 - gin.H{
 - "code": http.StatusOK,
 - "error": "Welcome server 01",
 - },
 - )
 - })
 - return e
 - }
 - func router02() http.Handler {
 - e := gin.New()
 - e.Use(gin.Recovery())
 - e.GET("/", func(c *gin.Context) {
 - c.JSON(
 - http.StatusOK,
 - gin.H{
 - "code": http.StatusOK,
 - "error": "Welcome server 02",
 - },
 - )
 - })
 - return e
 - }
 - func main() {
 - server01 := &http.Server{
 - Addr: ":8080",
 - Handler: router01(),
 - ReadTimeout: 5 * time.Second,
 - WriteTimeout: 10 * time.Second,
 - }
 - server02 := &http.Server{
 - Addr: ":8081",
 - Handler: router02(),
 - ReadTimeout: 5 * time.Second,
 - WriteTimeout: 10 * time.Second,
 - }
 - g.Go(func() error {
 - err := server01.ListenAndServe()
 - if err != nil && err != http.ErrServerClosed {
 - log.Fatal(err)
 - }
 - return err
 - })
 - g.Go(func() error {
 - err := server02.ListenAndServe()
 - if err != nil && err != http.ErrServerClosed {
 - log.Fatal(err)
 - }
 - return err
 - })
 - if err := g.Wait(); err != nil {
 - log.Fatal(err)
 - }
 - }
 - /*
 - 模擬訪問服務(wù)1:
 - curl http://localhost:8080/
 - {"code":200,"error":"Welcome server 01"}
 - 模擬訪問服務(wù)2:
 - curl http://localhost:8081/
 - {"code":200,"error":"Welcome server 02"}
 - */
 
優(yōu)雅的關(guān)閉和重啟服務(wù)
有一些方法可以優(yōu)雅的關(guān)閉或者重啟服務(wù), 比如不應(yīng)該中斷活動(dòng)的連接, 需要優(yōu)雅等待服務(wù)完成后才執(zhí)行關(guān)閉或重啟. 你可以使用第三方包來實(shí)現(xiàn), 也可以使用內(nèi)置的包自己實(shí)現(xiàn)優(yōu)雅關(guān)閉或重啟.
使用第三方包
fvbock/endless 包, 可以實(shí)現(xiàn)Golang HTTP/HTTPS服務(wù)的零停機(jī)和優(yōu)雅重啟(Golang版本至少1.3以上)
我們可以使用fvbock/endless 替代默認(rèn)的 ListenAndServe方法, 更多詳情, 請參考問題#296.
- router := gin.Default()
 - router.GET("/", handler)
 - // [...]
 - endless.ListenAndServe(":4242", router)
 
其他替代包:
- manners: 一個(gè)優(yōu)雅的Go HTTP服務(wù), 可以優(yōu)雅的關(guān)閉服務(wù).
 - graceful: 優(yōu)雅的Go包, 能夠優(yōu)雅的關(guān)閉一個(gè)http.Handler服務(wù)
 - grace: 該包為Go服務(wù)實(shí)現(xiàn)優(yōu)雅重啟, 零停機(jī)
 
手動(dòng)實(shí)現(xiàn)
如果你使用Go1.8或者更高的版本, 你可能不需要使用這些庫. 可以考慮使用http.Server的內(nèi)置方法Shutdown()來優(yōu)雅關(guān)閉服務(wù). 下面的示例描述了基本用法, 更多示例請參考這里
- // +build go1.8
 - package main
 - import (
 - "context"
 - "log"
 - "net/http"
 - "os"
 - "os/signal"
 - "syscall"
 - "time"
 - "github.com/gin-gonic/gin"
 - )
 - func main() {
 - router := gin.Default()
 - router.GET("/", func(c *gin.Context) {
 - time.Sleep(5 * time.Second)
 - c.String(http.StatusOK, "Welcome Gin Server")
 - })
 - srv := &http.Server{
 - Addr: ":8080",
 - Handler: router,
 - }
 - // Initializing the server in a goroutine so that
 - // it won't block the graceful shutdown handling below
 - // 用協(xié)程初始化一個(gè)服務(wù), 它不會(huì)阻塞下面的優(yōu)雅邏輯處理
 - go func() {
 - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
 - log.Fatalf("listen: %s\n", err)
 - }
 - }()
 - // Wait for interrupt signal to gracefully shutdown the server with
 - // a timeout of 5 seconds.
 - //等待一個(gè)操作系統(tǒng)的中斷信號(hào), 來優(yōu)雅的關(guān)閉服務(wù)
 - quit := make(chan os.Signal)
 - // kill (no param) default send syscall.SIGTERM //kill會(huì)發(fā)送終止信號(hào)
 - // kill -2 is syscall.SIGINT //發(fā)送強(qiáng)制進(jìn)程結(jié)束信號(hào)
 - // kill -9 is syscall.SIGKILL but can't be catch, so don't need add it //發(fā)送SIGKILL信號(hào)給進(jìn)程
 - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
 - <-quit //阻塞在這里,直到獲取到一個(gè)上面的信號(hào)
 - log.Println("Shutting down server...")
 - // The context is used to inform the server it has 5 seconds to finish
 - // the request it is currently handling
 - //這里使用context上下文包, 有5秒鐘的處理超時(shí)時(shí)間
 - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 - defer cancel()
 - if err := srv.Shutdown(ctx); err != nil { //利用內(nèi)置Shutdown方法優(yōu)雅關(guān)閉服務(wù)
 - log.Fatal("Server forced to shutdown:", err)
 - }
 - log.Println("Server exiting")
 - }
 
參考文檔
Gin官方倉庫:https://github.com/gin-gonic/gin
















 
 
 








 
 
 
 