您现在的位置是:亿华云 > 人工智能

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

亿华云2025-10-02 09:00:37【人工智能】9人已围观

简介简介本文接着上文(Golang GinWeb框架7-静态文件/模板渲染)继续探索GinWeb框架.重定向Gin返回一个HTTP重定向非常简单, 使用Redirect方法即可. 内部和外部链接都支持.p

 简介

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

重定向

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       // 在中间件或者控制器中启动协程时, 不能直接使用原来的上下文, 必须使用一个只读的云南idc服务商上线文副本       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() } 

支持LetsEncrypt证书加密处理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 wont 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 cant be catch, so dont 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

云服务器提供商

很赞哦!(9114)