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

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

[[356340]]

 简介

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

重定向

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

  1. package main 
  2.  
  3. import ( 
  4.   "github.com/gin-gonic/gin" 
  5.   "net/http" 
  6.  
  7. func main() { 
  8.   r := gin.Default() 
  9.   r.GET("/test", func(c *gin.Context) { 
  10.     c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")  //重定向到外部链接 
  11.   }) 
  12.  
  13.   //重定向到内部链接 
  14.   r.GET("/internal", func(c *gin.Context) { 
  15.     c.Redirect(http.StatusMovedPermanently, "/home"
  16.   }) 
  17.  
  18.   r.GET("/home", func(c *gin.Context) { 
  19.     c.JSON(200, gin.H{"msg""这是首页"}) 
  20.   }) 
  21.   r.Run(":8080"
  22.  
  23. /* 
  24. 重定向到外部链接,访问:http://localhost:8080/test 
  25. 重定向到内部链接,访问:http://localhost:8080/internal 
  26. */ 

从POST方法中完成HTTP重定向, 参考问题#444 https://github.com/gin-gonic/gin/issues/444

  1. r.POST("/test", func(c *gin.Context) { 
  2.   c.Redirect(http.StatusFound, "/foo"
  3. }) 

如果要产生一个路由重定向, 类似上面的内部重定向, 则使用 HandleContext方法, 像下面这样使用:

  1. r.GET("/test", func(c *gin.Context) { 
  2.     c.Request.URL.Path = "/test2" 
  3.     r.HandleContext(c) 
  4. }) 
  5. r.GET("/test2", func(c *gin.Context) { 
  6.     c.JSON(200, gin.H{"hello""world"}) 
  7. }) 

自定义中间件

  1. package main 
  2.  
  3. import ( 
  4.   "github.com/gin-gonic/gin" 
  5.   "log" 
  6.   "time" 
  7.  
  8. //自定义日志中间件 
  9. func Logger() gin.HandlerFunc { 
  10.   return func(c *gin.Context) { 
  11.     t := time.Now() 
  12.  
  13.     // Set example variable 在gin上下文中设置键值对 
  14.     c.Set("example""12345"
  15.  
  16.     // before request 
  17.  
  18.     //Next方法只能用于中间件中,在当前中间件中, 从方法链执行挂起的处理器 
  19.     c.Next() 
  20.  
  21.     // after request  打印中间件执行耗时 
  22.     latency := time.Since(t) 
  23.     log.Print(latency) 
  24.  
  25.     // access the status we are sending  打印本中间件的状态码 
  26.     status := c.Writer.Status() 
  27.     log.Println(status) 
  28.   } 
  29.  
  30. func main() { 
  31.   r := gin.New() 
  32.   //使用该自定义中间件 
  33.   r.Use(Logger()) 
  34.   r.GET("/test", func(c *gin.Context) { 
  35.     example := c.MustGet("example").(string) //从上下文中获取键值对 
  36.     // it would print: "12345" 
  37.     log.Println(example) 
  38.   }) 
  39.  
  40.   // Listen and serve on 0.0.0.0:8080 
  41.   r.Run(":8080"

使用基本认证BasicAuth()中间件

  1. package main 
  2.  
  3. import ( 
  4.   "github.com/gin-gonic/gin" 
  5.   "net/http" 
  6.  
  7. // simulate some private data 
  8. var secrets = gin.H{ 
  9.   "foo":    gin.H{"email""foo@bar.com""phone""123433"}, 
  10.   "austin": gin.H{"email""austin@example.com""phone""666"}, 
  11.   "lena":   gin.H{"email""lena@guapa.com""phone""523443"}, 
  12.  
  13. func main() { 
  14.   r := gin.Default() 
  15.  
  16.   // Group using gin.BasicAuth() middleware 
  17.   // gin.Accounts is a shortcut for map[string]string 
  18.   // 路由组authorized使用基本认证中间件, 参数为gin.Accounts,是一个map,键名是用户名, 键值是密码, 该中间件会将认证信息保存到cookie中 
  19.   authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{ 
  20.     "foo":    "bar"
  21.     "austin""1234"
  22.     "lena":   "hello2"
  23.     "manu":   "4321"
  24.   })) 
  25.  
  26.   // /admin/secrets endpoint 
  27.   // hit "localhost:8080/admin/secrets 
  28.   authorized.GET("/secrets", func(c *gin.Context) { 
  29.     // get user, it was set by the BasicAuth middleware 
  30.     // 从cookie中获取用户认证信息, 键名为user 
  31.     user := c.MustGet(gin.AuthUserKey).(string) 
  32.     if secret, ok := secrets[user]; ok { 
  33.       c.JSON(http.StatusOK, gin.H{"user"user"secret": secret}) 
  34.     } else { 
  35.       c.JSON(http.StatusOK, gin.H{"user"user"secret""NO SECRET :("}) 
  36.     } 
  37.   }) 
  38.  
  39.   // Listen and serve on 0.0.0.0:8080 
  40.   r.Run(":8080"
  41. /* 
  42. 测试访问:http://localhost:8080/admin/secrets 
  43. */ 

在中间件中使用协程Goroutines

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

  1. package main 
  2.  
  3. import ( 
  4.   "github.com/gin-gonic/gin" 
  5.   "log" 
  6.   "time" 
  7.  
  8. func main() { 
  9.   r := gin.Default() 
  10.  
  11.   r.GET("/long_async", func(c *gin.Context) { 
  12.     // create copy to be used inside the goroutine 
  13.     // 创建一个Gin上下文的副本, 准备在协程Goroutine中使用 
  14.     cCp := c.Copy() 
  15.     go func() { 
  16.       // simulate a long task with time.Sleep(). 5 seconds 
  17.       // 模拟长时间任务,这里是5秒 
  18.       time.Sleep(5 * time.Second
  19.  
  20.       // note that you are using the copied context "cCp", IMPORTANT 
  21.       // 在中间件或者控制器中启动协程时, 不能直接使用原来的上下文, 必须使用一个只读的上线文副本 
  22.       log.Println("Done! in path " + cCp.Request.URL.Path) 
  23.     }() 
  24.   }) 
  25.  
  26.   r.GET("/long_sync", func(c *gin.Context) { 
  27.     // simulate a long task with time.Sleep(). 5 seconds 
  28.     time.Sleep(5 * time.Second
  29.  
  30.     // since we are NOT using a goroutine, we do not have to copy the context 
  31.     // 没有使用协程时, 可以直接使用Gin上下文 
  32.     log.Println("Done! in path " + c.Request.URL.Path) 
  33.   }) 
  34.  
  35.   // Listen and serve on 0.0.0.0:8080 
  36.   r.Run(":8080"
  37. /* 
  38. 模拟同步阻塞访问:http://localhost:8080/long_sync 
  39. 模拟异步非阻塞访问:http://localhost:8080/long_async 
  40. */ 

自定义HTTP配置

直接使用 http.ListenAndServe()方法:

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

或者自定义HTTP配置

  1. func main() { 
  2.   router := gin.Default() 
  3.  
  4.   s := &http.Server{ 
  5.     Addr:           ":8080"
  6.     Handler:        router, 
  7.     ReadTimeout:    10 * time.Second
  8.     WriteTimeout:   10 * time.Second
  9.     MaxHeaderBytes: 1 << 20, 
  10.   } 
  11.   s.ListenAndServe() 

支持Let'sEncrypt证书加密处理HTTPS

下面是一行式的LetsEncrypt HTTPS服务

  1. package main 
  2.  
  3. import ( 
  4.   "log" 
  5.  
  6.   "github.com/gin-gonic/autotls" 
  7.   "github.com/gin-gonic/gin" 
  8.  
  9. func main() { 
  10.   r := gin.Default() 
  11.  
  12.   // Ping handler 
  13.   r.GET("/ping", func(c *gin.Context) { 
  14.     c.String(200, "pong"
  15.   }) 
  16.   //一行式LetsEncrypt证书, 处理https 
  17.   log.Fatal(autotls.Run(r, "example1.com""example2.com")) 

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

  1. package main 
  2.  
  3. import ( 
  4.   "log" 
  5.  
  6.   "github.com/gin-gonic/autotls" 
  7.   "github.com/gin-gonic/gin" 
  8.   "golang.org/x/crypto/acme/autocert" 
  9.  
  10. func main() { 
  11.   r := gin.Default() 
  12.  
  13.   // Ping handler 
  14.   r.GET("/ping", func(c *gin.Context) { 
  15.     c.String(200, "pong"
  16.   }) 
  17.  
  18.   m := autocert.Manager{ 
  19.     Prompt:     autocert.AcceptTOS, //Prompt指定一个回调函数有条件的接受证书机构CA的TOS服务, 使用AcceptTOS总是接受服务条款 
  20.     HostPolicy: autocert.HostWhitelist("example1.com""example2.com"),  //HostPolicy用于控制指定哪些域名, 管理器将检索新证书 
  21.     Cache:      autocert.DirCache("/var/www/.cache"),  //缓存证书和其他状态 
  22.   } 
  23.  
  24.   log.Fatal(autotls.RunWithManager(r, &m)) 

详情参考autotls包

使用Gin运行多个服务

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

  1. package main 
  2.  
  3. import ( 
  4.   "log" 
  5.   "net/http" 
  6.   "time" 
  7.  
  8.   "github.com/gin-gonic/gin" 
  9.   "golang.org/x/sync/errgroup" 
  10.  
  11. var ( 
  12.   g errgroup.Group 
  13.  
  14. func router01() http.Handler { 
  15.   e := gin.New() 
  16.   e.Use(gin.Recovery()) 
  17.   e.GET("/", func(c *gin.Context) { 
  18.     c.JSON( 
  19.       http.StatusOK, 
  20.       gin.H{ 
  21.         "code":  http.StatusOK, 
  22.         "error""Welcome server 01"
  23.       }, 
  24.     ) 
  25.   }) 
  26.  
  27.   return e 
  28.  
  29. func router02() http.Handler { 
  30.   e := gin.New() 
  31.   e.Use(gin.Recovery()) 
  32.   e.GET("/", func(c *gin.Context) { 
  33.     c.JSON( 
  34.       http.StatusOK, 
  35.       gin.H{ 
  36.         "code":  http.StatusOK, 
  37.         "error""Welcome server 02"
  38.       }, 
  39.     ) 
  40.   }) 
  41.  
  42.   return e 
  43.  
  44. func main() { 
  45.   server01 := &http.Server{ 
  46.     Addr:         ":8080"
  47.     Handler:      router01(), 
  48.     ReadTimeout:  5 * time.Second
  49.     WriteTimeout: 10 * time.Second
  50.   } 
  51.  
  52.   server02 := &http.Server{ 
  53.     Addr:         ":8081"
  54.     Handler:      router02(), 
  55.     ReadTimeout:  5 * time.Second
  56.     WriteTimeout: 10 * time.Second
  57.   } 
  58.  
  59.   g.Go(func() error { 
  60.     err := server01.ListenAndServe() 
  61.     if err != nil && err != http.ErrServerClosed { 
  62.       log.Fatal(err) 
  63.     } 
  64.     return err 
  65.   }) 
  66.  
  67.   g.Go(func() error { 
  68.     err := server02.ListenAndServe() 
  69.     if err != nil && err != http.ErrServerClosed { 
  70.       log.Fatal(err) 
  71.     } 
  72.     return err 
  73.   }) 
  74.  
  75.   if err := g.Wait(); err != nil { 
  76.     log.Fatal(err) 
  77.   } 
  78.  
  79. /* 
  80. 模拟访问服务1: 
  81. curl http://localhost:8080/ 
  82. {"code":200,"error":"Welcome server 01"
  83.  
  84. 模拟访问服务2: 
  85. curl http://localhost:8081/ 
  86. {"code":200,"error":"Welcome server 02"
  87. */ 

优雅的关闭和重启服务

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

使用第三方包

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

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

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

其他替代包:

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

手动实现

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

  1. // +build go1.8 
  2.  
  3. package main 
  4.  
  5. import ( 
  6.   "context" 
  7.   "log" 
  8.   "net/http" 
  9.   "os" 
  10.   "os/signal" 
  11.   "syscall" 
  12.   "time" 
  13.  
  14.   "github.com/gin-gonic/gin" 
  15.  
  16. func main() { 
  17.   router := gin.Default() 
  18.   router.GET("/", func(c *gin.Context) { 
  19.     time.Sleep(5 * time.Second
  20.     c.String(http.StatusOK, "Welcome Gin Server"
  21.   }) 
  22.  
  23.   srv := &http.Server{ 
  24.     Addr:    ":8080"
  25.     Handler: router, 
  26.   } 
  27.  
  28.   // Initializing the server in a goroutine so that 
  29.   // it won't block the graceful shutdown handling below 
  30.   // 用协程初始化一个服务, 它不会阻塞下面的优雅逻辑处理 
  31.   go func() { 
  32.     if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { 
  33.       log.Fatalf("listen: %s\n", err) 
  34.     } 
  35.   }() 
  36.  
  37.   // Wait for interrupt signal to gracefully shutdown the server with 
  38.   // a timeout of 5 seconds. 
  39.   //等待一个操作系统的中断信号, 来优雅的关闭服务 
  40.   quit := make(chan os.Signal) 
  41.   // kill (no param) default send syscall.SIGTERM  //kill会发送终止信号 
  42.   // kill -2 is syscall.SIGINT  //发送强制进程结束信号 
  43.   // kill -9 is syscall.SIGKILL but can't be catch, so don't need add it  //发送SIGKILL信号给进程 
  44.   signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 
  45.   <-quit //阻塞在这里,直到获取到一个上面的信号 
  46.   log.Println("Shutting down server..."
  47.  
  48.   // The context is used to inform the server it has 5 seconds to finish 
  49.   // the request it is currently handling 
  50.   //这里使用context上下文包, 有5秒钟的处理超时时间 
  51.   ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second
  52.   defer cancel() 
  53.   if err := srv.Shutdown(ctx); err != nil {  //利用内置Shutdown方法优雅关闭服务 
  54.     log.Fatal("Server forced to shutdown:", err) 
  55.   } 
  56.  
  57.   log.Println("Server exiting"

参考文档

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

 

责任编辑:姜华 来源: 云原生云
相关推荐

2020-12-10 10:22:48

GinWeb中间件HTTPS

2020-11-25 09:18:15

Golang GinW

2016-11-11 21:00:46

中间件

2020-12-02 11:18:28

Golang GinW

2020-11-25 09:10:39

Golang GinW

2021-10-06 19:03:35

Go中间件Middleware

2024-01-05 08:17:53

FiberGolang路由

2023-05-08 08:09:26

路由元信息谓词

2021-01-20 08:26:16

中间件技术spring

2020-11-23 10:48:39

Golang GinW

2024-05-06 12:30:51

Go语言中间件

2011-05-24 15:10:48

2021-02-11 08:21:02

中间件开发CRUD

2018-07-29 12:27:30

云中间件云计算API

2018-02-01 10:19:22

中间件服务器系统

2020-01-07 08:00:52

ApacheHTTPHTTPS

2023-06-29 10:10:06

Rocket MQ消息中间件

2012-11-30 10:21:46

移动中间件

2009-06-16 15:55:06

JBoss企业中间件

2023-10-24 07:50:18

消息中间件MQ
点赞
收藏

51CTO技术栈公众号