Golang GinWeb框架8-重定向/自定义中间件/认证/HTTPS支持/优雅重启等

简介

本文接着上文(Golang GinWeb框架7-静态文件/模板渲染)继续探索GinWeb框架.

Golang GinWeb框架8-重定向/自定义中间件/认证/HTTPS支持/优雅重启等

重定向

Gin返回一个HTTP重定向非常简单, 使用Redirect方法即可. 内部和外部链接都支持.

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/")  //重定向到外部链接 
  }) 
 
  //重定向到内部链接 
  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 
重定向到内部链接,访问: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") 
}) 

如果要产生一个路由重定向, 类似上面的内部重定向, 则使用 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上下文中设置键值对 
    c.Set("example", "12345") 
 
    // before request 
 
    //Next方法只能用于中间件中,在当前中间件中, 从方法链执行挂起的处理器 
    c.Next() 
 
    // after request  打印中间件执行耗时 
    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) { 
    example := c.MustGet("example").(string) //从上下文中获取键值对 
    // it would print: "12345" 
    log.Println(example) 
  }) 
 
  // Listen and serve on 0.0.0.0:8080 
  r.Run(":8080") 
} 

使用基本认证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使用基本认证中间件, 参数为gin.Accounts,是一个map,键名是用户名, 键值是密码, 该中间件会将认证信息保存到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中获取用户认证信息, 键名为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 
*/ 

在中间件中使用协程Goroutines

在中间件或者控制器中启动新协程时, 不能直接使用原来的Gin上下文, 必须使用一个只读的上下文副本

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 
    // 创建一个Gin上下文的副本, 准备在协程Goroutine中使用 
    cCp := c.Copy() 
    go func() { 
      // simulate a long task with time.Sleep(). 5 seconds 
      // 模拟长时间任务,这里是5秒 
      time.Sleep(5 * time.Second) 
 
      // note that you are using the copied context "cCp", IMPORTANT 
      // 在中间件或者控制器中启动协程时, 不能直接使用原来的上下文, 必须使用一个只读的上线文副本 
      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 
    // 没有使用协程时, 可以直接使用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服务

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

自定义自动证书管理器autocert manager实例代码:

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指定一个回调函数有条件的接受证书机构CA的TOS服务, 使用AcceptTOS总是接受服务条款 
    HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"),  //HostPolicy用于控制指定哪些域名, 管理器将检索新证书 
    Cache:      autocert.DirCache("/var/www/.cache"),  //缓存证书和其他状态 
  } 
 
  log.Fatal(autotls.RunWithManager(r, &m)) 
} 

详情参考autotls包

使用Gin运行多个服务

可以在主函数中使用协程Goroutine运行多个服务, 每个服务端口不同, 路由分组也不同. 请参考这个问题, 尝试运行以下示例代码:

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) 
  } 
} 
 
/* 
模拟访问服务1: 
curl http://localhost:8080/ 
{"code":200,"error":"Welcome server 01"} 
 
模拟访问服务2: 
curl http://localhost:8081/ 
{"code":200,"error":"Welcome server 02"} 
*/ 

优雅的关闭和重启服务

有一些方法可以优雅的关闭或者重启服务, 比如不应该中断活动的连接, 需要优雅等待服务完成后才执行关闭或重启. 你可以使用第三方包来实现, 也可以使用内置的包自己实现优雅关闭或重启.

使用第三方包

fvbock/endless 包, 可以实现Golang HTTP/HTTPS服务的零停机和优雅重启(Golang版本至少1.3以上)

我们可以使用fvbock/endless 替代默认的 ListenAndServe方法, 更多详情, 请参考问题#296.

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

其他替代包:

  • manners: 一个优雅的Go HTTP服务, 可以优雅的关闭服务.
  • graceful: 优雅的Go包, 能够优雅的关闭一个http.Handler服务
  • grace: 该包为Go服务实现优雅重启, 零停机

手动实现

如果你使用Go1.8或者更高的版本, 你可能不需要使用这些库. 可以考虑使用http.Server的内置方法Shutdown()来优雅关闭服务. 下面的示例描述了基本用法, 更多示例请参考这里

// +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 
  // 用协程初始化一个服务, 它不会阻塞下面的优雅逻辑处理 
  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. 
  //等待一个操作系统的中断信号, 来优雅的关闭服务 
  quit := make(chan os.Signal) 
  // kill (no param) default send syscall.SIGTERM  //kill会发送终止信号 
  // kill -2 is syscall.SIGINT  //发送强制进程结束信号 
  // kill -9 is syscall.SIGKILL but can't be catch, so don't need add it  //发送SIGKILL信号给进程 
  signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 
  <-quit //阻塞在这里,直到获取到一个上面的信号 
  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秒钟的处理超时时间 
  ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 
  defer cancel() 
  if err := srv.Shutdown(ctx); err != nil {  //利用内置Shutdown方法优雅关闭服务 
    log.Fatal("Server forced to shutdown:", err) 
  } 
 
  log.Println("Server exiting") 
} 

参考文档

Gin官方仓库:https://github.com/gin-gonic/gin

© 版权声明
THE END
感觉文章不错,分享给身边的朋友吧!
点赞5赞赏
分享