一、Gin框架
1. 版本信息
GO语言版本 1.16
Gin框架版本 v1.7.7
使用软件:
- vscode
- vim 【 VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Mar 18 2022 09:44:47) 】、
2. 导入
1.下载并安装 gin:
$ go get -u github.com/gin-gonic/gin
2.将 gin 引入到代码中:
import "github.com/gin-gonic/gin"
3.(可选)如果使用诸如 http.StatusOK
之类的常量,则需要引入 net/http
包:
import "net/http"
二、待学习知识点
1. 快速开始响应一个JSON字符串
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.POST("/ding", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "dong",
})
})
r.Run() // 监听并在 0.0.0.0:8080 上启动服务
// 如果想要监听其他接口的话可以直接写 :端口号 例如下面这个案例
// r.Run(":8001")
}
解释: 0.0.0.0:8080
表示监听本机任意IP的,如果本机只有一个IP可以忽略下面的讲解,这个主要是为具有多个IP的主机设置的。
因为有时候一台主机会同时具有多个IP地址,这个时候我们想要多个IP的8080端口都可以访问我们的服务的话,一个一个设置监听就太麻烦了,所以就有了上面的这个
0.0.0.0
这个特殊的IP。 这个主要是由系统的路由控制的,默认情况监听流程如下:
- 首先客户端访问本机的一个IP下的8080
- 如果有服务监听这个IP下的8080端口
- 就让监听该IP的8080端口的服务来处理相应的请求
- 但是假如该IP中没有匹配到相应的服务,这个时候路由就会将相应的服务跳转到
0.0.0.0:8080
- 这个时候如果
0.0.0.0
中8080端口有相应的服务,就由监听0.0.0.0
的服务处理相应的请求。- 否则就返回拒绝访问
2. AsciiJSON
生成具有转义的非 ASCII 字符的 ASCII-only JSON。 【官方文档的介绍,可能会有点绕口】
func main() {
r := gin.Default()
r.GET("/someJSON", func(c *gin.Context) {
data := map[string]interface{}{
"lang": "GO语言",
"tag": "<br>",
}
// 输出 : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}
c.AsciiJSON(http.StatusOK, data)
})
// 监听并在 0.0.0.0:8080 上启动服务
r.Run(":8080")
}
3. HTML渲染
3.1 同一个文件夹下面的HTML模板
文件结构如下:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.LoadHTMLGlob("temp/*") //解析
router.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "test html template",
})
})
router.Run(":8000")
}
<!DOCTYPE html>
<html lang="zh-cn">
<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}}</title>
</head>
<body>
{{ .title }}
<h1>initialization</h1>
</body>
</html>
3.2 不同目录下名称相同的模板
例如:
//main.go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.LoadHTMLGlob("templates/**/*")
router.GET("/posts/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
"title": "Posts",
})
})
router.GET("/users/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
"title": "Users",
})
})
router.Run(":8080")
}
- templates/posts/index.html
{{ define "posts/index.html" }}
<html><h1>
{{ .title }}
</h1>
<p>Using posts/index.tmpl</p>
</html>
{{ end }}
- templates/users/index.html
{{ define "users/index.html" }}
<html><h1>
{{ .title }}
</h1>
<p>Using users/index.tmpl</p>
</html>
{{ end }}
4. (空) HTTP2 server推送
5. JSONP
有时候我们需要写一点回调函数,通过JSONP函数来写回调
func main() {
router := gin.Default()
router.GET("/index", func(ctx *gin.Context) {
ctx.JSONP(http.StatusOK, gin.H{
"test": "ziop",
})
})
router.Run(":8000")
}
5.1 测试用例
- 请求地址
http://127.0.0.1:8000/index?callback=ziop
- 输出结果
ziop({"test":"ziop"});
5.2 理解JSONP
JSONP的源码
// JSONP serializes the given struct as JSON into the response body. // It adds padding to response body to request data from a server residing in a different domain than the client. // It also sets the Content-Type as "application/javascript". func (c *Context) JSONP(code int, obj interface{}) { callback := c.DefaultQuery("callback", "") if callback == "" { c.Render(code, render.JSON{Data: obj}) return } c.Render(code, render.JsonpJSON{Callback: callback, Data: obj}) }
根据源代码我们可以知道其实可以知道,默认是检测了一下访问的时候是否添加了 callback
这个查询参数,如果没有加这个参数的话是按照正常的JSON流程来进行处理的,跟原来的一样。
{"test":"ziop"}
如果加上了callback
这个参数的话就会这个时候就会执行这个回调,返回回调的函数。
ziop({"test":"ziop"});
-
Multipart/Urlencoded绑定
-
Multipart/Urlencoded表单
-
PureSON
-
Query和post form
-
SecureJSON
-
XML/SON/YAMU/ProtoBuf渲染
-
上传文件
-
不使用默认的中间件
-
从reader读取数据
-
优雅地重启或停止
使用BasicAuth中间件
gin框架设置最基础的登录限制方式如下。
package main import ( "net/http" "github.com/fvbock/endless" "github.com/gin-gonic/gin" ) // 模拟数据库 的内容 var secrets = gin.H{ "ziop": gin.H{"phone": 110, "sex": "man"}, "raw": gin.H{"phone": 666, "sex": "woman"}, } func main() { e := gin.Default() // gin.Accounts{} 实际上是一个 map[string]string,这里面写的账号是可以登陆的账号 auth := e.Group("/admin", gin.BasicAuth(gin.Accounts{ "ziop": "ziop", "raw": "raw", "test": "test", })) auth.GET("/secrets", func(ctx *gin.Context) { user := ctx.MustGet(gin.AuthUserKey).(string) if secrets, ok := secrets[user]; ok { ctx.JSON(http.StatusOK, gin.H{ "user": user, "secrets": secrets, }) } else { ctx.JSON(http.StatusOK, gin.H{ "user": user, "secrets": "NO SECRET :(", }) } }) endless.ListenAndServe(":8000", e) }
测试
- 在浏览器输入相应的连接,如果是本机操作默认是
127.0.0.1:8000/admin/secrets
- 这时候浏览器就会有一个弹出框,提示我们需要登录
- 在这里输入的账号密码就是我们在路有组里面写的那几组账号密码
- 然后就登录成功可以正常访问了
- 在浏览器输入相应的连接,如果是本机操作默认是
-
使用HTTP方法
6. 中间件
介绍
中间件在B/S模式中的架构的作用
中间件在B/S模式下起到了功能层的作用。当用户从WEB界面向服务器提交了数据请求或者应用请求时,功能层负责将这些请求分类为数据或应用请求,再向数据库发出数据交换申请。数据库对请求进行筛选处理之后,再将所需的数据通过功能层传递回到用户端。通过如此处理,单一用户可以进行点对面的操作,无需通过其他软件进行数据转换。
—节选自《中间件》百度百科
形象理解,中间件就像洋葱一样,一层一层的,先执行最外层然后一层一层往里面进入
中间件的分类
==全局中间件==、==路由组中间件==、==单个路由中间件==
中间件设置方式
全局中间件
**介绍:**全局中间件设置之后对全局的路由都起作用
**注册方式:**通过默认路由来设置。
分类:
- 一次设置一个中间件
- 一次设置多个中间件。
router := gin.New()
//一次设置多个中间件
router.Use(Logger(), Recovery())
//一次设置一个中间件
router.Use(gin.Logger())
router.Use(gin.Recovery())
路由组中间件
**介绍:**路由组中间件仅对该路由组下面的路由起作用
设置方式:
-
在声明路由组的同时设置路由组中间件
-
authorized := router.Group("/", CookieMiddleware())
-
-
先声明路由组然后再通过
use
进行设置-
authorized := router.Group("/") authorized.Use(CookieMiddleware())
-
单个路由中间件
**介绍:**单个路由中间件仅对一个路由起作用
设置方式:
- 单个路由设置单个中间件
router.GET("/login", LoginMiddleware, handler)
- 单个路由设置多个中间件
router.GET("/mid", TimeMiddleware, CookieMiddleware(), handler)
中间件的两个专属方法
ctx.Next() 继续
在程序进入中间件的时候需要先进行一些处理,然后去 执行核心业务,在执行完核心业务之后再回来执行该中间件。
跟Java Spring 里面的AOP很像
router.GET("/mid", TimeMiddleware, handler)
// 记录函数执行的时间中间件
func TimeMiddleware(ctx *gin.Context) {
fmt.Println("------------TimeMiddleware 计时开始------------")
start := time.Now()
ctx.Next()
Since := time.Since(start)
fmt.Println(Since)
fmt.Println("++++++++++++TimeMiddleware 计时结束++++++++++++")
}
func handler(ctx *gin.Context) {
fmt.Println("#############正常的handler执行开始#############")
ctx.JSON(http.StatusOK, gin.H{
"ziop": "ziop",
})
}
ctx.Abort() 中断
在程序进入中间件之后我们进行了一些操作,判断该用户不满足访问这个请求的条件,这个时候我们就需要终止这个请求,不让其继续执行,这个时候就使用到了Abort
router.GET("/login", LoginMiddleware, handler)
func LoginMiddleware(ctx *gin.Context) {
fmt.Println("------------LoginMiddleware 计时开始------------")
login := ctx.Query("login")
if login != "" {
ctx.Next()
fmt.Println("Hi, " + login + ",欢迎访问ziop的小屋")
} else {
ctx.Abort()
fmt.Println("未登录,拒绝访问")
}
fmt.Println("++++++++++++LoginMiddleware 计时结束++++++++++++")
}
-
测试一
-
测试二
-
本质
Gin中的中间件实际上还是一个Gin中的 gin.HandlerFunc
,也就是说他和其他的处理器都差不多,通过中间件的使用可以使多个处理器同时处理一个请求。
如果学过Javaweb或者说学过spring boot的同学可以将Gin里面的中间件理解为拦截器(filter)之类的工具。
使用场景
场景一 权限验证
有些网页在访问的时候需要用户登录之后才可以发送请求,这个时候我们就必须写一个处理器来检测用户是否登录如果登录的话就可以正常访问,如果用户没有登录就拒绝请求的处理。
起初,我们当让可以使用一个处理器来写: 这个处理器先判断一下用户是否登录然后再执行相应的处理。一个接口可以这样写,两个接口可以这样写。但是当需求量逐渐增多的时候我们发现这样写的话就会很麻烦。
这个时候我们发现可以抽出来一个公共的方法来进行验证用户是否登录。然后我们的核心业务代码就会大大减少了,同时也提升了开发的效率。这样我们对需要限制访问的方法加上这个公共的方法,不需要限制的方法不加上这个公共方法就简单的实现了我们的需求。
然而这个公共的方法就是我们这里要提到的中间件。
场景二 检测时间
有时候我们想要检测一下某个请求的处理时间,来做一些日志,以便于以后有错误需要排查的时候方便排查。这时候就可以写一个中间件来检测处理时间。
router.GET("/mid", TimeMiddleware, handler)
// 记录函数执行的时间中间件
func TimeMiddleware(ctx *gin.Context) {
fmt.Println("------------TimeMiddleware 计时开始------------")
start := time.Now()
ctx.Next()
Since := time.Since(start)
fmt.Println(Since)
fmt.Println("++++++++++++TimeMiddleware 计时结束++++++++++++")
}
func handler(ctx *gin.Context) {
fmt.Println("#############正常的handler执行开始#############")
ctx.JSON(http.StatusOK, gin.H{
"ziop": "ziop",
})
}
场景三 数据处理
我们有可能在正式处理核心业务之前会先对请求的数据进行一些处理,这个时候也可以使用到中间件。就像下面的案例就是在处理之前先进行了一次Cookie的设置。
router.GET("/mid", CookieMiddleware(), handler)
func CookieMiddleware() gin.HandlerFunc {
return func(ctx *gin.Context) {
fmt.Println("------------CookieMiddleware start------------")
user := ctx.Query("user")
fmt.Printf("ctx.Request.Host: %v\n", strings.Split(ctx.Request.Host, ":")[0])
ctx.SetCookie("user", user, 0, "", strings.Split(ctx.Request.Host, ":")[0], false, true)
fmt.Println("++++++++++++CookieMiddleware end++++++++++++")
}
}
整个案例代码
package main
import (
"fmt"
"net/http"
"strings"
"time"
"github.com/fvbock/endless"
"github.com/gin-gonic/gin"
)
func main() {
// 新建一个没有任何默认中间件的路由
router := gin.New()
//----------------------------------------------------------------------
// 全局中间件
// 全局中间件
// Logger 中间件将日志写入 gin.DefaultWriter,即使你将 GIN_MODE 设置为 release。
// gin.DefaultWriter = os.Stdout 默认的日志输出是Stdout 也就是控制台输出
router.Use(gin.Logger())
// Recovery 中间件会 recover 任何 panic。如果有 panic 的话,会写入 500。
// 这样的好处就是一旦有一个服务不能正常运行了,也只是会影响到一个请求。
// 不会影响到整个服务器的运行,不至于导致整个项目的瘫痪
router.Use(gin.Recovery())
//----------------------------------------------------------------------
//路有组中间件
// 认证路由组
// authorized := router.Group("/", AuthRequired())
// 和使用以下两行代码的效果完全一样:
authorized := router.Group("/admin")
// 路由组中间件! 在此例中,我们在 "authorized" 路由组中使用自定义创建的
// AuthRequired() 中间件
authorized.Use(TimeMiddleware)
{
authorized.POST("/login", handler)
authorized.POST("/submit", handler)
authorized.POST("/read", handler)
// 嵌套路由组
testing := authorized.Group("testing")
testing.GET("/analytics", handler)
}
//----------------------------------------------------------------------
//单个路由中间件
//单独为某个路由设置中间件
router.GET("/mid", TimeMiddleware, CookieMiddleware(), handler)
router.GET("/login", LoginMiddleware, handler)
// 优雅的退出服务
endless.ListenAndServe(":8000", router)
}
func handler(ctx *gin.Context) {
fmt.Println("#############正常的handler执行开始#############")
ctx.JSON(http.StatusOK, gin.H{
"ziop": "ziop",
})
}
func CookieMiddleware() gin.HandlerFunc {
return func(ctx *gin.Context) {
fmt.Println("------------CookieMiddleware start------------")
user := ctx.Query("user")
fmt.Printf("ctx.Request.Host: %v\n", strings.Split(ctx.Request.Host, ":")[0])
ctx.SetCookie("user", user, 0, "", strings.Split(ctx.Request.Host, ":")[0], false, true)
fmt.Println("++++++++++++CookieMiddleware end++++++++++++")
}
}
// 记录函数执行的时间中间件
func TimeMiddleware(ctx *gin.Context) {
fmt.Println("------------TimeMiddleware 计时开始------------")
start := time.Now()
ctx.Next()
Since := time.Since(start)
fmt.Println(Since)
fmt.Println("++++++++++++TimeMiddleware 计时结束++++++++++++")
}
// 登录检测中间件
func LoginMiddleware(ctx *gin.Context) {
fmt.Println("------------LoginMiddleware 计时开始------------")
login := ctx.Query("login")
if login != "" {
ctx.Next()
fmt.Println("Hi, " + login + ",欢迎访问ziop的小屋")
} else {
ctx.Abort()
fmt.Println("未登录,拒绝访问")
}
fmt.Println("++++++++++++LoginMiddleware 计时结束++++++++++++")
}
- 只绑定ur查询字符串
- 在中间件中使用Goroutine多模板
7. 日志
如何记录日志
func main() {
// 禁用控制台颜色,将日志写入文件时不需要控制台颜色。
gin.DisableConsoleColor()
// 记录到文件。
f, _ := os.Create("gin.log")
gin.DefaultWriter = io.MultiWriter(f)
// 如果需要同时将日志写入文件和控制台,请使用以下代码。
// gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
router := gin.Default()
router.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
router.Run(":8080")
}
定义路由日志的格式
控制日志输出颜色
自定义日志文件
-
将request body绑定到不同的结构体中
-
支持Let's Encrypt
-
映射查询字符串或表单参数
-
查询字符串参数
-
模型绑定和验证
-
绑定HTML复选框
-
绑定Uri
-
邦定查询字符串或表单数据
-
绑定表单数据至自定义结构体
-
自定义HTP配置
-
自定义验证器
-
设置和获取Cookie
-
路由参数
路由组
func main() { router := gin.Default() // 简单的路由组: v1 v1 := router.Group("/v1") { v1.POST("/login", loginEndpoint) v1.POST("/submit", submitEndpoint) v1.POST("/read", readEndpoint) } // 简单的路由组: v2 v2 := router.Group("/v2") { v2.POST("/login", loginEndpoint) v2.POST("/submit", submitEndpoint) v2.POST("/read", readEndpoint) } router.Run(":8080") }
-
运行多个服务
-
重定向
-
静态文件服务
-
静态资源嵌入
文档持续更新中………