Dawn's Blogs

分享技术 记录成长

0%

Gin基本使用 (5) 服务器推送和优雅的退出

服务器推送

概念

在传统 HTTP 请求中,一个 HTML 页面如果包含了其他图片、CSS 样式表等外部资源,需要发送多个请求(多轮请求耗时)

服务器推送就是为了解决这样一种问题,服务器推送(server push)指的是,还没有收到浏览器的请求,服务器就把各种资源推送给浏览器

比如,浏览器只请求了 index.html,但是服务器把 index.htmlstyle.cssexample.png 全部发送给浏览器。这样的话,只需要一轮 HTTP 通信,浏览器就得到了全部资源,提高了性能

对于服务器推送,有一个很麻烦的问题。所要推送的资源文件,如果浏览器已经有缓存,推送就是浪费带宽。即使推送的文件版本更新,浏览器也会优先使用本地缓存

一种解决办法是,只对第一次访问的用户开启服务器推送。

Gin server push

http.Pusher 推送仅支持 **Go 1.8+**,Gin 中使用 c.Push() 获取 http.Pusher:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func main() {
r := gin.Default()
r.Static("/assets", "./assets")
r.SetHTMLTemplate(html)

r.GET("/", func(c *gin.Context) {
if pusher := c.Writer.Pusher(); pusher != nil {
// 使用 pusher.Push() 做服务器推送
if err := pusher.Push("/assets/app.js", nil); err != nil {
log.Printf("Failed to push: %v", err)
}
}
c.HTML(200, "https", gin.H{
"status": "success",
})
})

// 监听并在 https://127.0.0.1:8080 上启动服务
r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key")
}

优雅的关闭服务器

http.Shutdown

在 Go 1.8 中,考虑使用 http 包自带的 Shutdown() 方法优雅的关闭服务器。该方法需要传入一个 Context 参数,当程序终止时其中不会中断活跃的连接,会等待活跃连接闲置Context 终止(手动 cancel 或超时)最后才终止程序

特别注意:

  • server.ListenAndServe() 方法在 Shutdown 时会立刻返回,Shutdown 方法会阻塞至所有连接闲置或 context 完成,所以 Shutdown 的方法要写在主 goroutine 中

  • 如果写在其他协程中,需要配合 sync.WaitGroup 来同步等待

Gin 中使用 http.Shutdwon

在具体用应用中我们可以配合 signal.Notify 函数来监听系统退出信号,来完成程序优雅退出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// +build go1.8

package main

import (
"context"
"log"
"net/http"
"os"
"os/signal"
"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,
}

go func() {
// 服务连接
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()

// 等待中断信号以优雅地关闭服务器(设置 5 秒的超时时间)
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt)
<-quit
log.Println("Shutdown Server ...")

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
log.Println("Server exiting")
}