火币HTX
火币是全球三大交易所之一,虚拟货币交易平台安全可靠,注册领取新人礼包!
简介
本文接着上文(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
// 在中间件或者控制器中启动协程时, 不能直接使用原来的上下文, 必须使用一个只读的上线文副本
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
网站名称:玩转网
本文链接:
版权声明:知识共享署名-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)协议进行许可
本站资源仅供个人学习交流,转载时请以超链接形式标明文章原始出处,(如有侵权联系删除)