2.Gin之Go语言Web框架基础配置实践
|[TOC]
0x00 如何自定义Gin服务配置及其启动多个服务?
描述: 在Gin的生产环境中通常会自定义HTTP配置以达到最优性能,此处我们简单一下 Server 结构体中可配置的参数项。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// A Server defines parameters for running an HTTP server.
// The zero value for Server is a valid configuration.
type Server struct {
// 配置监听地址:端口,默认是:8080
Addr string
// 要调用的处理程序,http.DefaultServeMux如果为nil
Handler Handler
// 如果为true,则将“OPTIONS*”请求传递给Handler
DisableGeneralOptionsHandler bool
// 提供TLS配置
TLSConfig *tls.Config
//读取整个请求(包括正文)的最长持续时间。
ReadTimeout time.Duration
// 读取整请求(Header)的最长持续时间。
ReadHeaderTimeout time.Duration
// 超时写入响应之前的最长持续时间
WriteTimeout time.Duration
// 启用保持活动时等待下一个请求的最长时间
IdleTimeout time.Duration
// 控制服务器解析请求标头的键和值(包括请求行)时读取的最大字节数 (通常情况下不进行设置)
MaxHeaderBytes int
// 在发生ALPN协议升级时接管所提供TLS连接的所有权。
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
// 指定了一个可选的回调函数,当客户端连接更改状态时调用该函数
ConnState func(net.Conn, ConnState)
// 为接受连接的错误、处理程序的意外行为以及潜在的FileSystem错误指定了一个可选的记录器
ErrorLog *log.Logger
// 返回/此服务器上传入请求的基本上下文
BaseContext func(net.Listener) context.Context
// 指定一个函数来修改用于新连接c的上下
ConnContext func(ctx context.Context, c net.Conn) context.Context
// 当服务器处于关闭状态时为true
inShutdown atomic.Bool
disableKeepAlives atomic.Bool
nextProtoOnce sync.Once // guards setupHTTP2_* init
nextProtoErr error // result of http2.ConfigureServer if used
mu sync.Mutex
listeners map[*net.Listener]struct{}
activeConn map[*conn]struct{}
onShutdown []func()
listenerGroup sync.WaitGroup
}
模块更新1
2go get -u golang.org/x/sync/errgroup
go mod tidy
示例代码: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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85package main
import (
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
)
// 处理属于同一总体任务的子任务的goroutine的集合
var (
g errgroup.Group
)
// s2 Gin 服务的 Handler
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,
"msg": "Welcome server 02 blog.weiyigeek.top",
},
)
})
return e
}
func main() {
// Default返回一个Engine实例,该实例已连接Logger和Recovery中间件。
router := gin.Default()
// Gin 服务s1.用于运行HTTP服务器的参数 (常规参数)
s1 := &http.Server{
// Gin运行的监听端口
Addr: ":8080",
// 要调用的处理程序,http.DefaultServeMux如果为nil
Handler: router,
// ReadTimeout是读取整个请求(包括正文)的最长持续时间。
ReadTimeout: 5 * time.Second,
// WriteTimeout是超时写入响应之前的最长持续时间
WriteTimeout: 10 * time.Second,
// MaxHeaderBytes控制服务器解析请求标头的键和值(包括请求行)时读取的最大字节数 (通常情况下不进行设置)
MaxHeaderBytes: 1 << 20,
}
// Go在一个新的goroutine中调用给定的函数,此处将Go语言的并发体现的淋漓尽致。
g.Go(func() error {
return s1.ListenAndServe()
})
// 配置Gin中间件
// Recovery返回一个中间件,该中间件可以从任何exception中恢复,并在出现exception时写入500。
router.Use(gin.Recovery())
// 服务s1的路由
router.GET("/", func(c *gin.Context) {
c.JSON(
http.StatusOK,
gin.H{
"code": http.StatusOK,
"msg": "Welcome server 01 www.weiyigeek.top",
},
)
})
// Gin 服务s1.定义了不同的监听端口以及Handler
s2 := &http.Server{
Addr: ":8081",
Handler: router02(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
g.Go(func() error {
return s2.ListenAndServe()
})
if err := g.Wait(); err != nil {
log.Fatal(err)
}
}
执行结果:
0x01 如何优雅的关闭或者重启Gin应用程序?
1.使用 chan 通道监听中断信号(SIGINT和SIGTERM)
描述: 在Go Gin中,可以使用以下代码实现优雅地重启或停止, 确保所有连接都被正确关闭,避免数据丢失或损坏。
代码示例: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
48
49
50
51
52
53
54
55
56
57package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
)
func main() {
// 创建 Gin 实例
router := gin.Default()
// 添加路由
router.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello, World! weiyigeek.top")
})
// 创建 HTTP Server
srv := &http.Server{
Addr: ":8080",
Handler: router,
}
// 开启一个goroutine启动服务 启动 HTTP Server
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// 等待中断信号
quit := make(chan os.Signal)
// kill 默认会发送 syscall.SIGTERM 信号
// kill -2 发送 syscall.SIGINT 信号,我们常用的Ctrl+C就是触发系统SIGINT信号
// kill -9 发送 syscall.SIGKILL 信号,但是不能被捕获,所以不需要添加它
// signal.Notify把收到的 syscall.SIGINT或syscall.SIGTERM 信号转发给quit
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) // 此处不会阻塞
<-quit // 阻塞在此,当接收到上述两种信号时才会往下执行
log.Println("Shutdown Server ...")
// 创建一个 5 秒的超时上下文
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 关闭 HTTP Server
// // 5秒内优雅关闭服务(将未处理完的请求处理完再关闭服务),超过5秒就超时退出
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
log.Println("Server exiting")
}
代码解析:
首先创建了一个Gin实例和一个HTTP Server,然后启动HTTP Server。接下来,使用signal.Notify()
函数监听中断信号(SIGINT和SIGTERM),当接收到中断信号时,服务器会进入优雅关闭流程,即先关闭HTTP Server
,然后等待5秒钟,最后退出程序。
在关闭HTTP Server时,我们使用了srv.Shutdown()
函数,它会优雅地关闭HTTP Server并等待所有连接关闭。如果在5秒钟内没有关闭完所有连接,函数会返回错误。
知识补充: 使用os/signal包实现对信号的处理, 最常见的信号列表。
2.使用 os/exec 包来执行Gin平滑重启
描述: 在Linux的Go-gin环境中我们可以使用 os/exec 包来执行重启命令,然后在 Gin 中定义一个路由,使得访问该路由时会执行重启命令。
代码示例: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
27package main
import (
"fmt"
"net/http"
"os"
"os/exec"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 重启的路由 /restart
r.GET("/restart", func(c *gin.Context) {
cmd := exec.Command("killall", "-HUP", "appweiyigeek")
err := cmd.Run()
if err != nil {
fmt.Println("Error executing restart command:", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to restart Gin server."})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Gin server restarted successfully."})
})
r.Run(":8080")
}
编译执行:1
2go build ./main.go -o appweiyigeek
./appweiyigeek
在上面的例子中,我们定义了一个路由 /restart,当访问该路由时,它会执行 killall -HUP appweiyigeek
命令来重启 Gin 服务, 这里的appweiyigeek
应该替换为你实际的 Gin 应用程序的名称。
温馨提示: 此种重启方式可能会导致请求失败或者超时,因为它会强制关闭正在处理的连接, 如果你需要更加优雅的重启方式,可以考虑使用优雅重启的方式。
3.使用 fvbock/endless 包实现访问指定路由平滑重启Gin服务
描述: 由于endless在windows环境是不支持,所以博主针对下述代码在Linux环境下载并编译成二进制文件打包到Linux环境运行进行验证。
依赖下载:1
2go get -u github.com/fvbock/endless
go mod tidy
代码示例: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
48
49
50
51
52
53
54
55
56
57
58
59
60
61package main
import (
"fmt"
"log"
"net/http"
"os/exec"
"strconv"
"syscall"
"github.com/fvbock/endless"
"github.com/gin-gonic/gin"
)
func main() {
pid := syscall.Getpid()
// 1.默认的Gin引擎
router := gin.Default()
// 传统方式
// server := &http.Server{
// Addr: ":8080",
// Handler: router,
// ReadTimeout: 5 * time.Second,
// WriteTimeout: 10 * time.Second,
// }
// 2.获取 Pid
router.GET("/pid", func(c *gin.Context) {
pid = syscall.Getpid()
fmt.Println("Pid:", pid)
c.JSON(http.StatusOK,
gin.H{
"code": http.StatusOK,
"msg": fmt.Sprintf("Gin Server Pid -> %d.", pid),
})
})
// 3.重启 Gin 服务
router.POST("/restart", func(c *gin.Context) {
pid = syscall.Getpid()
fmt.Println("Restarting Gin Server.......", pid)
err := exec.Command("kill", "-1", strconv.Itoa(pid)).Run()
if err != nil {
fmt.Println("Error executing restart command:", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to restart Gin server."})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Gin server restarted successfully.", "pid": pid})
})
// 4.使用endless侦听TCP网络地址addr,然后使用处理程序调用Serve来处理传入连接上的请求
err := endless.ListenAndServe(":8080", router)
if err != nil || err != http.ErrServerClosed {
log.Println("err:", err)
}
// 5.引入了endless扩展,将原本的Run方式启动项目改成了ListenAndServe方式所有此处主席掉
// router.Run(":8080")
}
编译构建:1
2
3
4
5
6
7
8
9
10# 切换编译在Linux平台的64位可执行程序环境
go env -w CGO_ENABLED=0 GOOS=linux GOARCH=amd64
# 编译
go build -o endless-test-1 .\main.go
# 执行验证
chmod +x endless-test-1
nohup ./endless-test-1 &
[1] 1147978
执行效果:
1 | # GET 请求 10.20.176.101:8080/pid |
请求restart
后可以看见go-gin已经平滑重启了是不是很方便,效果如下。
知识扩展:
3、Windows下编译Mac, Linux平台的64位可执行程序:
1
2$ go env -w CGO_ENABLED=0 GOOS=darwin3 GOARCH=amd64
$ go env -w CGO_ENABLED=0 GOOS=linux GOARCH=amd642、Linux下编译Mac, Windows平台的64位可执行程序:
1
2$ go env -w CGO_ENABLED=0 GOOS=darwin GOARCH=amd64
$ go env -w CGO_ENABLED=0 GOOS=windows GOARCH=amd641、Mac下编译Linux, Windows平台的64位可执行程序:
1
2$ go env -w CGO_ENABLED=0 GOOS=linux GOARCH=amd64
$ go env -w CGO_ENABLED=0 GOOS=windows GOARCH=amd64
0x02 如何自定义 Gin 日志格式?
1.自定义定义路由日志的格式
描述: 此处介绍如何定义路由日志的格式,而非使用默认的路由访问日志格式。
例如:默认的路由日志格式 GIN-debug] POST /foo --> main.main.func1 (3 handlers)
, 如果你想要以指定的格式(例如 JSON,key values 或其他格式)记录信息,则可以使用 gin.DebugPrintRouteFunc 指定格式。
1 | package main |
执行效果:
2.自定义原生路由访问日志格式
描述: 此处是使用 gin.LoggerWithFormatter & gin.LogFormatterParams
实现自定义路由访问日志。
代码示例: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
48
49
50
51
52
53
54
55package main
import (
"fmt"
"io"
"os"
"time"
"github.com/gin-gonic/gin"
)
func main() {
// 强制日志颜色化
gin.ForceConsoleColor()
// 禁用控制台颜色
// gin.DisableConsoleColor()
// 默认为 debug 模式,设置为发布模式
gin.SetMode(gin.ReleaseMode)
// 将日志同时写入文件和控制台,请使用以下代码 f 表示文件,os,os.Stdout 表示终端
f, _ := os.Create("gin.log")
// 如果需要同时将日志写入文件和控制台,请使用以下代码。
gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
// 生成gin实例,即 WSGI 应用程序
r := gin.New()
// 自定义日志格式
// LoggerWithFormatter 中间件会将日志写入 gin.DefaultWriter
// By default => gin.DefaultWriter = os.Stdout
r.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
// 自定义格式
return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
param.ClientIP,
param.TimeStamp.Format(time.RFC1123),
param.Method,
param.Path,
param.Request.Proto,
param.StatusCode,
param.Latency,
param.Request.UserAgent(),
param.ErrorMessage,
)
}))
r.Use(gin.Recovery())
// 声明了一个路由及对应的处理函数 (匿名函数)
r.GET("/log", func(c *gin.Context) {
c.JSON(200, gin.H{
"code": "200", "msg": "Test gin logs", "data": "",
})
})
r.Run()
}
执行效果:
3.使用 log 自定义日志并按天分隔保存到文件
描述: 此处使用原生的 log 模块实现自定义路由日志,并载入到Gin的日志中间件中,实现终端与文件同时输出,输入的日志的文件按照天进行分隔,好了,废话不多说直接上代码:
日志中间件: middleware\Logger.go1
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package middleware
import (
"fmt"
"io"
"log"
"os"
"time"
"github.com/gin-gonic/gin"
)
// Logger 是一个自定义的日志中间件
func Logger() gin.HandlerFunc {
// 日志文件路径
logFilePath := "./logs/"
// 日志文件名前缀
logFileName := "weiyigeek"
// 日志文件后缀
logFileExt := "log"
// 日志文件最大大小,单位为 MB
logFileMaxSize := 1000
// 日志文件切割的时间间隔,单位为天
logFileSplitDays := 1
// 检查日志目录是否存在,不存在则创建
err := os.MkdirAll(logFilePath, os.ModePerm)
if err != nil {
log.Fatalf("Failed to create log directory: %v", err)
}
// 获取当前时间的年月日
now := time.Now()
year, month, day := now.Date()
// 构造日志文件名
logFileName = fmt.Sprintf("%s-%d-%02d-%02d", logFileName, year, month, day)
// 打开日志文件
logFile, err := os.OpenFile(
fmt.Sprintf("%s/%s.%s", logFilePath, logFileName, logFileExt),
os.O_WRONLY|os.O_APPEND|os.O_CREATE,
0666,
)
if err != nil {
log.Fatalf("Failed to open log file: %v", err)
}
// 设置日志输出
writers := []io.Writer{
logFile,
os.Stdout}
log.SetOutput(io.MultiWriter(writers...))
log.SetFlags(log.Ldate | log.Ltime | log.LUTC)
return func(c *gin.Context) {
// 处理请求前记录日志
// 开始时间
startTime := time.Now()
// 调用该请求的剩余处理程序
c.Next()
// 结束时间
endTime := time.Now()
// 执行时间
latencyTime := endTime.Sub(startTime)
// 请求IP
clientIP := c.ClientIP()
// remoteIP := c.RemoteIP()
// 请求方式
reqMethod := c.Request.Method
// 请求路由
reqUri := c.Request.RequestURI
// 请求协议
reqProto := c.Request.Proto
// 请求来源
repReferer := c.Request.Referer()
// 请求UA
reqUA := c.Request.UserAgent()
// 请求响应内容长度
resLength := c.Writer.Size()
if resLength < 0 {
resLength = 0
}
// 响应状态码
statusCode := c.Writer.Status()
log.Printf(
"%s | %3d | %s %10s | \033[44;37m%-6s\033[0m %s %s | %10v | \"%s\" \"%s\"",
colorForStatus(statusCode),
statusCode,
colorForStatus(0),
clientIP,
// remoteIP,
reqMethod,
reqUri,
reqProto,
latencyTime,
reqUA,
repReferer,
)
// 判断日志文件是否需要切割
fileInfo, err := logFile.Stat()
if err != nil {
log.Fatalf("Failed to get log file info: %v", err)
}
_, _, lastDay := endTime.AddDate(0, 0, -1*logFileSplitDays).Date()
if fileInfo.Size() > int64(logFileMaxSize*1024*1024) {
// 关闭当前日志文件
logFile.Close()
// 构造新的日志文件名
logFileName = fmt.Sprintf("%s-%s", logFileName, time.Now().Format("2006-01-02-15"))
// 创建新的日志文件
logFile, err = os.OpenFile(
fmt.Sprintf("%s/%s.%s", logFilePath, logFileName, logFileExt),
os.O_WRONLY|os.O_APPEND|os.O_CREATE,
0666,
)
if err != nil {
log.Fatalf("Failed to create log file: %v", err)
}
// 设置日志输出
log.SetOutput(logFile)
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
} else if fileInfo.ModTime().Day() == lastDay {
// 关闭当前日志文件
logFile.Close()
// 构造新的日志文件名
logFileName = fmt.Sprintf("%s-%s", logFileName, endTime.Format("2006-01-02"))
// 创建新的日志文件
logFile, err = os.OpenFile(
fmt.Sprintf("%s/%s.%s", logFilePath, logFileName, logFileExt),
os.O_WRONLY|os.O_APPEND|os.O_CREATE,
0666,
)
if err != nil {
log.Fatalf("Failed to create log file: %v", err)
}
// 设置日志输出
log.SetOutput(logFile)
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
}
}
}
// colorForStatus 根据 HTTP 状态码返回 ANSI 颜色代码
func colorForStatus(code int) string {
switch {
case code >= 200 && code < 300:
return "\033[42;1;37m" // green
case code >= 300 && code < 400:
return "\033[34m" // blue
case code >= 400 && code < 500:
return "\033[33m" // yellow
case code == 0:
return "\033[0m" // cancel
default:
return "\033[31m" // red
}
}
入口文件: main.go1
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62package main
import (
"devopsapi/middleware"
router "devopsapi/routers"
"fmt"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
)
// 处理属于同一总体任务的子任务的goroutine的集合
var (
g errgroup.Group
)
func main() {
// 指定 gin 运行模式
gin.SetMode(global.App.Mode)
// 返回一个新的空白Engine实例
r := gin.New()
// 设置日志中间件
r.Use(middleware.Logger())
// 加载自定义路由
router.Load(r)
// Linux、Mac 环境下使用 fvbock/endless 艰辛平滑重启
// err := endless.ListenAndServe(fmt.Sprintf("%s:%d", global.App.Host, global.App.Port), r)
// if err != nil || err != http.ErrServerClosed {
// log.Println("err:", err)
// }
// W通用:开放监听运行Gin服务
server := &http.Server{
// Gin运行的监听端口
Addr: ":8080",
// 要调用的处理程序,http.DefaultServeMux如果为nil
Handler: r,
// ReadTimeout是读取整个请求(包括正文)的最长持续时间。
ReadTimeout: 5 * time.Second,
// WriteTimeout是超时写入响应之前的最长持续时间
WriteTimeout: 10 * time.Second,
// MaxHeaderBytes控制服务器解析请求标头的键和值(包括请求行)时读取的最大字节数 (通常情况下不进行设置)
MaxHeaderBytes: 1 << 20,
}
// 创建 goroutine 中调用给定的函数
g.Go(func() error {
return server.ListenAndServe()
})
// goroutine 所有函数调用都返回,然后从中返回第一个非零错误(如果有的话)。
if err := g.Wait(); err != nil {
log.Fatal(err)
}
}
执行结果:
日志实现格式: 2023/06/09 09:59:04 [42;1;37m | 200 | [0m 10.20.172.103 | [44;37mGET [0m /app/version HTTP/1.1 | 144.4µs | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.57" ""
0x03 如何定义使用静态资源与HTML网页渲染
1.Gin中静态资源映射实践
描述: 此处使用graceful来管理我们的gin服务,能够平滑的停止Gin服务,然后又实践了静态资源映射三种方式的使用。
1 | go get -u gopkg.in/tylerb/graceful.v1 |
静态资源映射三种方式
- router.Static 指定某个目录为静态资源目录,可直接访问这个目录下的资源,url 要具体到资源名称。
- router.StaticFS 比前面一个多了个功能,当目录下不存 index.html 文件时,会列出该目录下的所有文件。
- router.StaticFile 指定某个具体的文件作为静态资源访问。
静态目录常规示例1
2
3
4
5
6
7
8
9
10# Linux Bash
mkdir -vp ./static/dist/assets/{css,js,img} ./storage/app/public
mkdir -vp ./templates/{index,posts,users}
# Windows PowerShell
mkdir ./static/dist/assets/css
mkdir ./static/dist/assets/js
mkdir ./static/dist/assets/img
mkdir ./storage/app/public
# mkdir ./templates/{index,posts,users}
代码示例: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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65package main
import (
"context"
"fmt"
"html/template"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
"gopkg.in/tylerb/graceful.v1"
)
func main() {
router := gin.Default()
// 1.静态资源映射
// 前端项目静态资源
router.StaticFile("/", "./static/dist/index.html")
router.StaticFile("/favicon.ico", "./static/dist/favicon.ico")
router.Static("/assets", "./static/dist/assets")
// 其他静态文件资源
router.Static("/public", "./static")
router.Static("/storage", "./storage/app/public")
// 使用 graceful 管理 Gin 服务从而优雅的停止
srv := &graceful.Server{
Timeout: 10 * time.Second,
Server: &http.Server{
Addr: ":8080",
Handler: router,
},
}
// 开启一个goroutine启动服务 启动 HTTP Server
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// 等待中断信号
quit := make(chan os.Signal)
// kill 默认会发送 syscall.SIGTERM 信号
// kill -2 发送 syscall.SIGINT 信号,我们常用的Ctrl+C就是触发系统SIGINT信号
// kill -9 发送 syscall.SIGKILL 信号,但是不能被捕获,所以不需要添加它
// signal.Notify把收到的 syscall.SIGINT或syscall.SIGTERM 信号转发给quit
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) // 此处不会阻塞
<-quit // 阻塞在此,当接收到上述两种信号时才会往下执行
log.Println("Shutdown Server ...")
// 创建一个 5 秒的超时上下文
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 关闭 HTTP Server
// // 5秒内优雅关闭服务(将未处理完的请求处理完再关闭服务),超过5秒就超时退出
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
log.Println("Server exiting")
}
访问效果:
2.Gin中网页HTML渲染实践
描述: 此处仍然在上述main.go中实践添加路由,以及在项目中添加templates模板文件, 模板文件目录结构如下:
1 | │ go.mod |
方式1.使用 LoadHTMLFiles 加载HTML文件的一部分,并将结果与HTML呈现器相关联。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// templates\index\index.tmpl
{{ define "index/index.tmpl" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTML 渲染 示例1</title>
</head>
<body>
<h1>{{.title}}</h1>
<p>HTML 渲染 示例2</p>
</body>
</html>
{{ end }}
// main.go 插入下述代码片段,以及此路由!
router.LoadHTMLFiles("templates/index/index.tmpl")
router.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index/index.tmpl", gin.H{"title": "HTML 渲染 示例1"})
})
执行效果:1
2[GIN] 2023/06/10 - 16:50:28 | 200 | 512.8µs | 127.0.0.1 | GET "/index"
[GIN] 2023/06/10 - 16:51:04 | 200 | 471µs | 127.0.0.1 | GET "/index"
方式2.LoadHTMLGlob加载由glob模式标识的HTML文件,并将结果与HTML呈现器相关联,并使用不同目录下名称相同的模板。
1 | // templates\posts\index.tmpl |
执行效果:
温馨提示: 如果模板在多级目录里面的话需要这样配置 r.LoadHTMLGlob("templates/**/**/*") /**
表示目录进行匹配。
方式3.自定义模板功能,即自定义变量分隔符以及模板中变量的处理函数
1 | // templates\users\index.tmpl |
执行效果:
3.Gin中HTML模板渲染语法总结
描述: 本小节主要讲解HTML模板渲染和模板语法、以及自定义模板函数,为Gin中展示HTML做一个铺垫。
演示模板
首先在项目根目录新建 templates 文件夹,然后在文件夹中新建admin文件以及对应的 index.html, 此处演示文件如下。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<!-- templates —> admin -> index.html -->
<!-- 给模板配置名称,用来区分不同文件夹下的模板,成对出现-->
{{ define "admin/index.html" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>后台管理页面</title>
</head>
<body>
<h1>这是一个后台管理页面的 html 模板</h1>
<h3>{{.title}}</h3>
</body>
</html>
{{ end }}
演示模板参数传入1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 片段示例
r.GET("/", func(c *gin.Context) {
c.HTML(200, "admin/index.html", gin.H{
"title": "hello gin templates",
"score": 92,
"hobby": []string{"带娃","吃饭", "睡觉", "coding"},
"testEmptySlice": []string{},
"news": &Article{
Title: "新闻标题",
Desc: "文章描述",
Content: "文章主体",
},
"timeStamp": 2953130242,
})
})
渲染模板语法
1.数据输出与变量赋值输出1
2
3
4
5
6
7
8<!-- // gin 中默认使用 {{}} 输出数据 -->
{{title}}
<!-- // gin 中变量赋值与输出 -->
<!-- 赋值给一个变量cj -->
{{ $cj := .score }}
<!-- 输出变量 -->
<h4>{{$cj}}</h4>
2.比较函数,布尔函数会将任何类型的的零值视为False否则为True.
1 | eq :等于,类似于 arg1 == ag2 则为True |
3.条件判断、range 循环遍历
1 | <!-- # 条件语句 --> |
4.with 解构结构体1
2
3
4
5
6<!-- with 结构结构体, 相当于将 news 赋值给 一个 点 -->
{{ with .news}}
{{.Title}}
{{.Desc}}
{{.Content}}
{{end}}
中间件中使用 go 协程
注意不能直接 在 go 程中使用原有的 Context操作
func Initmiddlewares(c *gin.Context) {
//使用协程 不能直接使用 原有 Context, 需要复制,然后操作复制的Context
Copycontext := c.Copy()
//使用 go 程统计日志
go func() {
time.Sleep(5 * time.Second)
fmt.Println("Done!!!" + Copycontext.Request.URL.Path)
}()
}
————————————————
版权声明:本文为CSDN博主「岚铭」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Ewige/article/details/128594610
公众号回复【0008】获取【Ubuntu22.04安装与加固建脚本】
公众号回复【10001】获取【WinServer安全加固脚本】
公众号回复【10002】获取【WinServer安全加固脚本】
公众号回复【0011】获取【k8S二进制安装部署教程】
公众号回复【0014】获取【Nginx学习之路汇总】
公众号回复【0015】获取【Jenkins学习之路汇总】
公众号回复【10005】获取【adb工具刷抖音赚米】
你好看友,欢迎关注博主微信公众号哟! ❤
这将是我持续更新文章的动力源泉,谢谢支持!(๑′ᴗ‵๑)
温馨提示: 未解锁的用户不能粘贴复制文章内容哟!
方式1.请访问本博主的B站【WeiyiGeek】首页关注UP主,
将自动随机获取解锁验证码。
Method 2.Please visit 【My Twitter】. There is an article verification code in the homepage.
方式3.扫一扫下方二维码,关注本站官方公众号
回复:验证码
将获取解锁(有效期7天)本站所有技术文章哟!
@WeiyiGeek - 为了能到远方,脚下的每一步都不能少
欢迎各位志同道合的朋友一起学习交流,如文章有误请在下方留下您宝贵的经验知识,个人邮箱地址【master#weiyigeek.top】
或者个人公众号【WeiyiGeek】
联系我。
更多文章来源于【WeiyiGeek Blog - 为了能到远方,脚下的每一步都不能少】, 个人首页地址( https://weiyigeek.top )
专栏书写不易,如果您觉得这个专栏还不错的,请给这篇专栏 【点个赞、投个币、收个藏、关个注、转个发、赞个助】,这将对我的肯定,我将持续整理发布更多优质原创文章!。
最后更新时间:
文章原始路径:_posts/编程世界/Go/Gin/2.Gin之Go语言Web框架基础配置实践.md
转载注明出处,原文地址:https://blog.weiyigeek.top/2023/6-2-745.html
本站文章内容遵循 知识共享 署名 - 非商业性 - 相同方式共享 4.0 国际协议