官方文档地址(中文):https://gin-gonic.com/zh-cn/docs/
注:本教程采用工作区机制,所以一个项目下载了Gin框架,其余项目就无需重复下载,想了解的读者可阅读第一节:Gin操作指南:开山篇。
本节演示服务器与高级功能,包括运行多个服务;优雅地重启或停止;重定向;支持Let’s-Encrypt;HTTP2-server推送。在开始之前,我们需要在”06服务器与高级功能“目录下打开命令行,执行如下命令来创建子目录:
mkdir 运行多个服务 优雅地重启或停止 重定向 支持Let's-Encrypt HTTP2-server推送
注意:四五两节需要下载安装openssl或者自己拥有一个域名,本文演示使用openssl的情况,openssl的下载与安装还请读者自行搜索,本站一堆。
目录
- 一、运行多个服务
- 二、优雅地重启或停止
- 三、重定向
- 四、支持Let's-Encrypt
- 五、HTTP2-server推送
一、运行多个服务
首先执行如下命令安装errgroup包
go get golang.org/x/sync/errgroup
然后填充代码
package main
import (
"log" // 引入日志包,用于记录错误
"net/http" // 引入HTTP包,用于创建HTTP服务器
"time" // 引入时间包,用于设置超时
"github.com/gin-gonic/gin" // 引入Gin框架,用于处理HTTP请求
"golang.org/x/sync/errgroup" // 引入errgroup包,用于处理并发任务和错误管理
)
// 声明一个errgroup.Group类型的变量,用于管理并发任务
var (
g errgroup.Group
)
// router01 创建第一个HTTP路由
func router01() http.Handler {
e := gin.New() // 创建一个新的Gin路由
e.Use(gin.Recovery()) // 使用Recovery中间件,防止因panic导致服务崩溃
// 定义GET请求的路由处理
e.GET("/", func(c *gin.Context) {
c.JSON( // 返回JSON格式的响应
http.StatusOK, // HTTP状态码200
gin.H{ // 返回的数据
"code": http.StatusOK, // 返回的状态码
"error": "Welcome server 01", // 返回的消息
},
)
})
return e // 返回路由
}
// router02 创建第二个HTTP路由
func router02() http.Handler {
e := gin.New() // 创建一个新的Gin路由
e.Use(gin.Recovery()) // 使用Recovery中间件,防止因panic导致服务崩溃
// 定义GET请求的路由处理
e.GET("/", func(c *gin.Context) {
c.JSON( // 返回JSON格式的响应
http.StatusOK, // HTTP状态码200
gin.H{ // 返回的数据
"code": http.StatusOK, // 返回的状态码
"error": "Welcome server 02", // 返回的消息
},
)
})
return e // 返回路由
}
// main函数是程序的入口
func main() {
// 创建第一个HTTP服务器,监听8080端口
server01 := &http.Server{
Addr: ":8080", // 服务器地址和端口
Handler: router01(), // 设置处理请求的路由
ReadTimeout: 5 * time.Second, // 设置读取超时时间
WriteTimeout: 10 * time.Second, // 设置写入超时时间
}
// 创建第二个HTTP服务器,监听8081端口
server02 := &http.Server{
Addr: ":8081", // 服务器地址和端口
Handler: router02(), // 设置处理请求的路由
ReadTimeout: 5 * time.Second, // 设置读取超时时间
WriteTimeout: 10 * time.Second, // 设置写入超时时间
}
// 启动第一个服务器的监听
g.Go(func() error {
return server01.ListenAndServe() // 启动服务器并监听请求
})
// 启动第二个服务器的监听
g.Go(func() error {
return server02.ListenAndServe() // 启动服务器并监听请求
})
// 等待所有服务器的goroutine完成,如果出错则记录并退出
if err := g.Wait(); err != nil {
log.Fatal(err) // 记录错误并退出程序
}
}
效果
二、优雅地重启或停止
优雅关闭确保在关闭服务器时,正在处理的请求能够完成。这意味着用户不会因为服务器突然停止而遭遇错误,提升用户体验。优雅关闭还允许应用在关闭前执行一些必要的清理操作,例如释放数据库连接、保存缓存、记录日志等,避免数据丢失或资源泄漏。下面演示如何实现。
如果版本低于1.8,可以考虑官方的建议:官方声明。
如果你使用的是 Go 1.8及以上版本,可以使用 http.Server 内置的 Shutdown() 方法优雅地关机:
//go:build go1.8
// +build go1.8
package main
import (
"context" // 引入 context 包,用于控制超时和取消操作
"log" // 引入 log 包,用于记录日志
"net/http" // 引入 net/http 包,用于创建 HTTP 服务器
"os" // 引入 os 包,用于与操作系统进行交互
"os/signal" // 引入 os/signal 包,用于接收系统信号
"time" // 引入 time 包,用于时间相关的操作
"github.com/gin-gonic/gin" // 引入 Gin 框架
)
func main() {
// 创建默认的 Gin 路由
router := gin.Default()
// 定义根路由处理函数
router.GET("/", func(c *gin.Context) {
// 模拟处理时间
time.Sleep(5 * time.Second)
// 返回字符串响应
c.String(http.StatusOK, "Welcome Gin Server")
})
// 创建 HTTP 服务器
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, 1) // 创建接收信号的通道
signal.Notify(quit, os.Interrupt) // 监听中断信号
<-quit // 阻塞直到收到信号
log.Println("Shutdown Server ...") // 输出服务器关闭信息
// 设置关闭服务器的上下文,并设置超时时间为 5 秒
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") // 输出服务器退出信息
}
效果
三、重定向
创建三个目录:get,post,router
1.GET重定向
cd到get目录
package main
import (
"net/http" // 导入 HTTP 包以使用 HTTP 相关功能
"github.com/gin-gonic/gin" // 导入 Gin 框架
)
func main() {
// 创建一个新的 Gin 引擎实例
r := gin.Default()
// 定义一个 GET 路由,当访问 /test 时触发该处理函数
r.GET("/test", func(c *gin.Context) {
// 使用 Redirect 方法进行重定向
// http.StatusMovedPermanently 表示 HTTP 301 状态码
// 第二个参数是目标 URL,这里重定向到 Google 的首页
c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
})
// 启动 HTTP 服务器,监听在 8080 端口
r.Run(":8080")
}
访问localhost:8080/test即可跳转到Google页面
2.POST重定向
cd到post目录
package main
import (
"net/http" // 导入 HTTP 包以使用 HTTP 相关功能
"github.com/gin-gonic/gin" // 导入 Gin 框架
)
func main() {
// 创建一个新的 Gin 引擎实例
r := gin.Default()
// 定义一个 GET 路由,当访问 /submit 时触发该处理函数
r.GET("/submit", func(c *gin.Context) {
// 返回一个 HTML 表单
form := `
<!DOCTYPE html>
<html>
<head>
<title>Submit Form</title>
</head>
<body>
<h1>Submit to Google</h1>
<form method="POST" action="/submit/do">
<input type="submit" value="Submit">
</form>
</body>
</html>
`
c.Header("Content-Type", "text/html") // 设置响应的内容类型为 HTML
c.String(http.StatusOK, form) // 返回表单
})
// 定义一个 POST 路由,当提交表单时触发该处理函数
r.POST("/submit/do", func(c *gin.Context) {
// 使用 Redirect 方法进行重定向
// http.StatusFound 表示 HTTP 302 状态码
// 第二个参数是重定向目标 URL,这里重定向到 Google
c.Redirect(http.StatusFound, "http://www.google.com")
})
// 启动 HTTP 服务器,监听在 8080 端口
r.Run(":8080")
}
运行,打开浏览器访问http://localhost:8080/submit,会出现提交页面
点击提交即可跳转到google
3.路由重定向
cd到router目录
package main
import (
"net/http" // 导入 HTTP 包以使用 HTTP 相关功能
"github.com/gin-gonic/gin" // 导入 Gin 框架
)
func main() {
// 创建一个新的 Gin 引擎实例
r := gin.Default()
// 定义一个 GET 路由,当访问 /test 时触发该处理函数
r.GET("/test", func(c *gin.Context) {
// 修改请求的 URL 路径,将其重定向到 /test2
c.Request.URL.Path = "/test2"
// 调用 r.HandleContext 方法,重新处理上下文
// 这会根据新的 URL 路径找到对应的处理函数
r.HandleContext(c)
})
// 定义一个 GET 路由,当访问 /test2 时触发该处理函数
r.GET("/test2", func(c *gin.Context) {
// 返回 JSON 响应,状态码为 200,内容为 {"hello": "world"}
c.JSON(http.StatusOK, gin.H{"hello": "world"})
})
// 启动 HTTP 服务器,监听在 8080 端口
r.Run(":8080")
}
注意,因为这次的路由路径还是和之前一样,所以如果你的浏览器再次跳转到google,清下cookie或把代码中的路由路径改下就行了。
四、支持Let’s-Encrypt
package main
import (
"log"
"net/http" // 导入 HTTP 包以使用 HTTP 相关功能
"github.com/gin-gonic/gin" // 导入 Gin 框架
)
func main() {
// 创建一个新的 Gin 引擎实例
r := gin.Default()
// 定义一个 Ping 路由,返回 "pong"
r.GET("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "pong")
})
// 启动 HTTPS 服务器,监听在 443 端口
err := r.RunTLS(":443", "cert.pem", "key.pem")
if err != nil {
log.Fatal("Failed to run server: ", err)
}
}
执行openssl命令,创建自签名证书
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes
以下是对该命令的解释
-x509: 生成一个自签名的证书。
-newkey rsa:2048: 创建一个新的 RSA 密钥,长度为 2048 位。
-keyout key.pem: 指定生成的私钥文件名。
-out cert.pem: 指定生成的证书文件名。
-days 365: 指定证书有效期为 365 天。
-nodes: 生成的私钥不使用密码保护(不需要输入密码)。
在命令执行过程中,会提示你输入一些信息,如国家、组织名称等,直接按回车跳过即可。
打开浏览器,以谷歌浏览器为例,访问https://localhost/ping,然后按下图操作
五、HTTP2-server推送
在“HTTP2-server推送”目录下新建目录testdata,并执行如下ssl命令,然后和上一节一样,一直按回车即可
openssl req -x509 -newkey rsa:2048 -keyout ./testdata/server.key -out ./testdata/server.pem -days 365 -nodes
新建main.go,填充代码
package main
import (
"html/template" // 导入模板包以处理 HTML 模板
"log" // 导入日志包以记录错误信息
"github.com/gin-gonic/gin" // 导入 Gin 框架
)
// 定义一个 HTML 模板
var html = template.Must(template.New("https").Parse(`
<html>
<head>
<title>Https Test</title>
<script src="/assets/app.js"></script> <!-- 引入 JavaScript 文件 -->
</head>
<body>
<h1 style="color:red;">Welcome, Ginner!</h1> <!-- 欢迎信息 -->
</body>
</html>
`))
func main() {
// 创建一个新的 Gin 引擎实例
r := gin.Default()
// 设置静态文件目录,允许访问 /assets 路径
r.Static("/assets", "./assets")
// 设置 HTML 模板
r.SetHTMLTemplate(html)
// 定义一个 GET 路由,当访问根路径时触发该处理函数
r.GET("/", func(c *gin.Context) {
// 检查是否支持 HTTP/2 Server Push
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) // 如果推送失败,记录错误
}
}
// 渲染 HTML 模板并返回给客户端
c.HTML(200, "https", gin.H{
"status": "success", // 返回状态
})
})
// 监听并在 https://127.0.0.1:8080 上启动服务
// 指定 SSL 证书和私钥的路径
r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key")
}
效果
访问https://127.0.0.1:8080/,然后步骤和第四节一样,最终返回如下页面
这里说明一下为什么要访问https://127.0.0.1:8080/而不是https://loaclhost:8080/:
虽然我们前面创建证书时是没有填写字段的,但127.0.0.1 是一种特殊的地址,即回环地址(127.0.0.0 到 127.255.255.255 ),这是系统保留用于本地主机通信的地址,即总是指向本机的地址,所以浏览器不管什么情况都会允许连接,显示警告是因为证书要求。相比之下,localhost 作为主机名,必须匹配证书,浏览器才会允许链接,所以当你访问localhost时,浏览器会显示无法访问此网站。